Initial commit

This commit is contained in:
jakweg 2020-08-24 17:47:57 +02:00
commit 4a411e1c98
28 changed files with 2627 additions and 0 deletions

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/build
/captures
.externalNativeBuild
.cxx
/.idea

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

32
app/build.gradle Normal file
View File

@ -0,0 +1,32 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 30
buildToolsVersion "30.0.1"
defaultConfig {
applicationId "pl.jakubweg"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.2.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

21
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="pl.jakubweg">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
tools:ignore="AllowBackup" />
</manifest>

View File

@ -0,0 +1,19 @@
package pl.jakubweg;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
public class Helper {
public static String getStringByName(Context context, String name) {
try {
Resources res = context.getResources();
return res.getString(res.getIdentifier(name, "string", context.getPackageName()));
} catch (Throwable exception) {
Log.e("XGlobals", "Resource not found.", exception);
return "";
}
}
}

View File

@ -0,0 +1,98 @@
package pl.jakubweg;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import java.lang.reflect.Field;
// invoke-static {p0}, Lpl/jakubweg/InjectedPlugin;->inject(Landroid/content/Context;)V
// invoke-static {}, Lpl/jakubweg/InjectedPlugin;->printSomething()V
// InlineTimeBar
public class InjectedPlugin {
private static final String TAG = "jakubweg.InjectedPlugin";
public static void printSomething() {
Log.d(TAG, "printSomething called");
}
public static void printObject(Object o, int recursive) {
if (o == null)
Log.d(TAG, "Printed object is null");
else {
Log.d(TAG, "Printed object ("
+ o.getClass().getName()
+ ") = " + o.toString());
for (Field field : o.getClass().getDeclaredFields()) {
if (field.getType().isPrimitive())
continue;
field.setAccessible(true);
try {
Object value = field.get(o);
try {
// if ("java.lang.String".equals(field.getType().getName()))
Log.d(TAG, "Field: " + field.toString() + " has value " + value);
} catch (Exception e) {
Log.d(TAG, "Field: " + field.toString() + " has value that thrown an exception in toString method");
}
if (recursive > 0 && value != null && !value.getClass().isPrimitive())
printObject(value, recursive - 1);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
public static void printObject(Object o) {
printObject(o, 0);
}
public static void printObject(int o) {
printObject(Integer.valueOf(o));
}
public static void printObject(float o) {
printObject(Float.valueOf(o));
}
public static void printObject(long o) {
printObject(Long.valueOf(o));
}
public static void printStackTrace() {
StackTraceElement[] stackTrace = (new Throwable()).getStackTrace();
Log.d(TAG, "Printing stack trace:");
for (StackTraceElement element : stackTrace) {
Log.d(TAG, element.toString());
}
}
public static void printViewStack(final View view, int spaces) {
StringBuilder builder = new StringBuilder(spaces);
for (int i = 0; i < spaces; i++) {
builder.append('-');
}
String spacesStr = builder.toString();
if (view == null) {
Log.i(TAG, spacesStr + "Null view");
return;
}
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
Log.i(TAG, spacesStr + "View group: " + view);
int childCount = group.getChildCount();
Log.i(TAG, spacesStr + "Children count: " + childCount);
for (int i = 0; i < childCount; i++) {
printViewStack(group.getChildAt(i), spaces + 1);
}
} else {
Log.i(TAG, spacesStr + "Normal view: " + view);
}
}
}

View File

@ -0,0 +1,139 @@
package pl.jakubweg;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import java.lang.ref.WeakReference;
public class NewSegmentHelperLayout extends LinearLayout implements View.OnClickListener {
private static final int rewindBtnId = 1235;
private static final int forwardBtnId = 1236;
private static final int publishBtnId = 1237;
private static final int hideBtnId = 1238;
private static final int markLocationBtnId = 1239;
private static final int previewBtnId = 1240;
private static final int editByHandBtnId = 1241;
private static WeakReference<NewSegmentHelperLayout> INSTANCE = new WeakReference<>(null);
private static boolean isShown = false;
private final int padding;
private final int iconSize;
private final int rippleEffectId;
private final String packageName;
@SuppressLint({"DefaultLocale", "SetTextI18n"})
public NewSegmentHelperLayout(Context context) {
super(context);
INSTANCE = new WeakReference<>(this);
isShown = false;
setVisibility(GONE);
packageName = context.getPackageName();
padding = (int) SkipSegmentView.convertDpToPixel(4f, context);
iconSize = (int) SkipSegmentView.convertDpToPixel(40f, context);
TypedValue rippleEffect = new TypedValue();
getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, rippleEffect, true);
rippleEffectId = rippleEffect.resourceId;
setOrientation(VERTICAL);
@SuppressLint("RtlHardcoded")
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.START | Gravity.LEFT | Gravity.CENTER_VERTICAL
);
this.setBackgroundColor(0x66000000);
this.bringToFront();
this.setLayoutParams(layoutParams);
this.setPadding(padding, padding, padding, padding);
final LinearLayout topLayout = new LinearLayout(context);
final LinearLayout bottomLayout = new LinearLayout(context);
topLayout.setOrientation(HORIZONTAL);
bottomLayout.setOrientation(HORIZONTAL);
this.addView(topLayout);
this.addView(bottomLayout);
topLayout.addView(createTextViewBtn(rewindBtnId, "player_fast_rewind"));
topLayout.addView(createTextViewBtn(forwardBtnId, "player_fast_forward"));
topLayout.addView(createTextViewBtn(markLocationBtnId, "ic_sb_adjust"));
bottomLayout.addView(createTextViewBtn(previewBtnId, "ic_sb_compare"));
bottomLayout.addView(createTextViewBtn(editByHandBtnId, "ic_sb_edit"));
bottomLayout.addView(createTextViewBtn(publishBtnId, "ic_sb_publish"));
// bottomLayout.addView(createTextViewBtn(hideBtnId,"btn_close_light"));
}
public static void show() {
if (isShown) return;
isShown = true;
NewSegmentHelperLayout i = INSTANCE.get();
if (i == null) return;
i.setVisibility(VISIBLE);
i.bringToFront();
i.requestLayout();
i.invalidate();
}
public static void hide() {
if (!isShown) return;
isShown = false;
NewSegmentHelperLayout i = INSTANCE.get();
if (i != null)
i.setVisibility(GONE);
}
public static void toggle() {
if (isShown) hide();
else show();
}
private View createTextViewBtn(int id, String drawableName) {
int drawableId = getResources().getIdentifier(drawableName, "drawable", packageName);
final ImageView view = new ImageView(getContext());
view.setPadding(padding, padding, padding, padding);
view.setLayoutParams(new LayoutParams(iconSize, iconSize, 1));
view.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
view.setImageResource(drawableId);
view.setId(id);
view.setClickable(true);
view.setFocusable(true);
view.setBackgroundResource(rippleEffectId);
view.setOnClickListener(this);
return view;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case forwardBtnId:
PlayerController.skipRelativeMilliseconds(SponsorBlockSettings.adjustNewSegmentMillis);
break;
case rewindBtnId:
PlayerController.skipRelativeMilliseconds(-SponsorBlockSettings.adjustNewSegmentMillis);
break;
case markLocationBtnId:
SponsorBlockUtils.onMarkLocationClicked(getContext());
break;
case publishBtnId:
SponsorBlockUtils.onPublishClicked(getContext());
break;
case previewBtnId:
SponsorBlockUtils.onPreviewClicked(getContext());
break;
case editByHandBtnId:
SponsorBlockUtils.onEditByHandClicked(getContext());
break;
case hideBtnId:
hide();
break;
}
}
}

View File

@ -0,0 +1,489 @@
package pl.jakubweg;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
@SuppressLint({"LongLogTag"})
public class PlayerController {
public static final String TAG = "jakubweg.PlayerController";
public static final boolean VERBOSE = false;
@SuppressWarnings("PointlessBooleanExpression")
public static final boolean VERBOSE_DRAW_OPTIONS = false && VERBOSE;
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;
private static final Runnable findAndSkipSegmentRunnable = new Runnable() {
@Override
public void run() {
// Log.d(TAG, "findAndSkipSegmentRunnable");
findAndSkipSegment(false);
}
};
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) {
Log.d(TAG, "setCurrentVideoId: videoId is null");
return;
}
if (!SponsorBlockSettings.isSponsorBlockEnabled) {
currentVideoId = null;
return;
}
if (Looper.myLooper() != Looper.getMainLooper()) // check if thread is not main
return;
if (videoId.equals(currentVideoId))
return;
currentVideoId = videoId;
sponsorSegmentsOfCurrentVideo = null;
if (VERBOSE)
Log.d(TAG, "setCurrentVideoId: videoId=" + videoId);
sponsorTimer.schedule(new TimerTask() {
@Override
public void run() {
executeDownloadSegments(currentVideoId, false);
}
}, 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) {
Log.e(TAG, "onCreate called with null object");
return;
}
if (VERBOSE)
Log.i(TAG, String.format("onCreate called with object %s on thread %s", o.toString(), Thread.currentThread().toString()));
try {
setMillisecondMethod = o.getClass().getMethod("a", Long.TYPE);
setMillisecondMethod.setAccessible(true);
lastKnownVideoTime = 0;
currentVideoLength = 1;
currentPlayerController = new WeakReference<>(o);
SkipSegmentView.hide();
NewSegmentHelperLayout.hide();
// add image button when starting new video
Activity activity = playerActivity.get();
if (activity != null)
SponsorBlockUtils.addImageButton(activity, 5);
} catch (Exception e) {
Log.e(TAG, "Exception while initializing skip method", e);
}
}
public static void executeDownloadSegments(String videoId, boolean ignoreCache) {
SponsorSegment[] segments = SponsorBlockUtils.getSegmentsForVideo(videoId, ignoreCache);
Arrays.sort(segments);
if (VERBOSE)
for (SponsorSegment segment : segments) {
Log.v(TAG, "Detected segment: " + segment.toString());
}
sponsorSegmentsOfCurrentVideo = segments;
// new Handler(Looper.getMainLooper()).post(findAndSkipSegmentRunnable);
}
/**
* Works in 14.x, waits some time of object to me filled with data,
* No longer used, i've found another way to get faster videoId
*/
@Deprecated
public static void asyncGetVideoLinkFromObject(final Object o) {
// code no longer used
// if (currentVideoLink != null) {
// if (VERBOSE)
// Log.w(TAG, "asyncGetVideoLinkFromObject: currentVideoLink != null probably share button was clicked");
// return;
// }
//
// new Thread(new Runnable() {
// @Override
// public void run() {
// try {
// // It used to be "b" in 14.x version, it's "a" in 15.x
// Field b = o.getClass().getDeclaredField("b");
//
// int attempts = 0;
// String videoUrl = null;
// while (true) {
// Object objLink = b.get(o);
// if (objLink == null) {
// if (VERBOSE)
// Log.e(TAG, "asyncGetVideoLinkFromObject: objLink is null");
// } else {
// videoUrl = objLink.toString();
// if (videoUrl.isEmpty())
// videoUrl = null;
// }
//
// if (videoUrl != null)
// break;
//
// if (attempts++ > 5) {
// Log.w(TAG, "asyncGetVideoLinkFromObject: attempts++ > 5");
// return;
// }
// Thread.sleep(50);
// }
//
// if (currentVideoLink == null) {
// currentVideoLink = videoUrl;
// if (VERBOSE)
// Log.d(TAG, "asyncGetVideoLinkFromObject: link set to " + videoUrl);
//
// executeDownloadSegments(substringVideoIdFromLink(videoUrl), false);
// }
//
// } catch (Exception e) {
// Log.e(TAG, "Cannot get link from object", e);
// }
// }
// }).start();
//
// Activity activity = playerActivity.get();
// if (activity != null)
// SponsorBlockUtils.addImageButton(activity);
}
/**
* 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) {
if (VERBOSE)
Log.v(TAG, "setCurrentVideoTime: current video time: " + millis);
if (!SponsorBlockSettings.isSponsorBlockEnabled) return;
lastKnownVideoTime = millis;
if (millis <= 0) return;
//findAndSkipSegment(false);
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) {
if (VERBOSE)
Log.d(TAG, "Scheduling skipSponsorTask");
skipSponsorTask = new TimerTask() {
@Override
public void run() {
skipSponsorTask = null;
lastKnownVideoTime = segment.start + 1;
new Handler(Looper.getMainLooper()).post(findAndSkipSegmentRunnable);
}
};
sponsorTimer.schedule(skipSponsorTask, segment.start - millis);
} else {
if (VERBOSE)
Log.d(TAG, "skipSponsorTask is already scheduled...");
}
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) {
new Thread(new Runnable() {
@Override
public void run() {
if (SponsorBlockSettings.countSkips &&
segment.category != SponsorBlockSettings.SegmentInfo.Preview &&
millis - segment.start < 2000) {
// Only skips from the start should count as a view
SponsorBlockUtils.sendViewCountRequest(segment);
}
}
}).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 (lastKnownVideoTime > 0)
lastKnownVideoTime = millis;
else
setCurrentVideoTime(millis);
}
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) {
if (VERBOSE_DRAW_OPTIONS)
Log.d(TAG, "setVideoLength: length=" + length);
currentVideoLength = length;
}
public static void setSponsorBarAbsoluteLeft(final Rect rect) {
setSponsorBarAbsoluteLeft(rect.left);
}
public static void setSponsorBarAbsoluteLeft(final float left) {
if (VERBOSE_DRAW_OPTIONS)
Log.d(TAG, String.format("setSponsorBarLeft: left=%.2f", left));
sponsorBarLeft = left;
}
public static void setSponsorBarAbsoluteRight(final Rect rect) {
setSponsorBarAbsoluteRight(rect.right);
}
public static void setSponsorBarAbsoluteRight(final float right) {
if (VERBOSE_DRAW_OPTIONS)
Log.d(TAG, String.format("setSponsorBarRight: right=%.2f", right));
sponsorBarRight = right;
}
public static void setSponsorBarThickness(final int thickness) {
setSponsorBarThickness((float) thickness);
}
public static void setSponsorBarThickness(final float thickness) {
if (VERBOSE_DRAW_OPTIONS)
Log.d(TAG, String.format("setSponsorBarThickness: thickness=%.2f", thickness));
sponsorBarThickness = thickness;
}
public static void onSkipSponsorClicked() {
if (VERBOSE)
Log.d(TAG, "Skip segment clicked");
findAndSkipSegment(true);
}
public static void addSkipSponsorView15(final View view) {
playerActivity = new WeakReference<>((Activity) view.getContext());
if (VERBOSE)
Log.d(TAG, "addSkipSponsorView15: view=" + view.toString());
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
final ViewGroup viewGroup = (ViewGroup) ((ViewGroup) view).getChildAt(2);
Activity context = ((Activity) viewGroup.getContext());
viewGroup.addView(new SkipSegmentView(context));
viewGroup.addView(new NewSegmentHelperLayout(context));
SponsorBlockUtils.addImageButton(context, 40);
}
}, 500);
}
public static void addSkipSponsorView14(final View view) {
playerActivity = new WeakReference<>((Activity) view.getContext());
if (VERBOSE)
Log.d(TAG, "addSkipSponsorView14: view=" + view.toString());
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
final ViewGroup viewGroup = (ViewGroup) view.getParent();
Activity activity = (Activity) viewGroup.getContext();
viewGroup.addView(new SkipSegmentView(activity));
viewGroup.addView(new NewSegmentHelperLayout(activity));
// add image button when creating new activity
SponsorBlockUtils.addImageButton(activity, 5);
// InjectedPlugin.printViewStack(viewGroup, 0);
// SponsorBlockUtils.addImageButton(activity);
}
}, 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) {
if (VERBOSE)
Log.w(TAG, "skipToMillisecond: to fast, slow down, because you'll fail");
return;
}
allowNextSkipRequestTime = now + 100;
if (setMillisecondMethod == null) {
Log.e(TAG, "setMillisecondMethod 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 skip to millis=%d on thread %s", millisecond, Thread.currentThread().toString()));
final long finalMillisecond = millisecond;
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
try {
if (VERBOSE)
Log.i(TAG, "Skipping to millis=" + finalMillisecond);
lastKnownVideoTime = finalMillisecond;
setMillisecondMethod.invoke(currentObj, finalMillisecond);
} catch (Exception e) {
Log.e(TAG, "Cannot skip to millisecond", e);
}
}
});
}
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;
if (VERBOSE)
Log.d(TAG, "Skipping segment: " + segment.toString());
if (SponsorBlockSettings.showToastWhenSkippedAutomatically && !wasClicked)
SkipSegmentView.notifySkipped(segment);
skipToMillisecond(segment.end + 2);
SkipSegmentView.hide();
if (segment.category == SponsorBlockSettings.SegmentInfo.Preview) {
SponsorSegment[] newSegments = new SponsorSegment[sponsorSegmentsOfCurrentVideo.length - 1];
int i = 0;
for (SponsorSegment sponsorSegment : sponsorSegmentsOfCurrentVideo) {
if (sponsorSegment != segment)
newSegments[i++] = sponsorSegment;
}
sponsorSegmentsOfCurrentVideo = newSegments;
}
}
}

View File

@ -0,0 +1,95 @@
package pl.jakubweg;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.lang.ref.WeakReference;
import static pl.jakubweg.Helper.getStringByName;
import static pl.jakubweg.PlayerController.VERBOSE;
@SuppressLint({"RtlHardcoded", "SetTextI18n", "LongLogTag"})
public class SkipSegmentView extends TextView implements View.OnClickListener {
public static final String TAG = "jakubweg.SkipSegmentView";
private static boolean isVisible = false;
private static WeakReference<SkipSegmentView> view = new WeakReference<>(null);
private static SponsorSegment lastNotifiedSegment;
public SkipSegmentView(Context context) {
super(context);
isVisible = false;
setVisibility(GONE);
view = new WeakReference<>(this);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.END | Gravity.RIGHT | Gravity.CENTER_VERTICAL
);
this.setLayoutParams(layoutParams);
this.setBackgroundColor(0x66000000);
// this.setBackgroundColor(Color.MAGENTA);
this.setTextColor(0xFFFFFFFF);
int padding = (int) convertDpToPixel(4, context);
setPadding(padding, padding, padding, padding);
this.setText("" + getStringByName(context, "tap_skip"));
setOnClickListener(this);
}
public static void show() {
if (isVisible) return;
SkipSegmentView view = SkipSegmentView.view.get();
if (VERBOSE)
Log.d(TAG, "show; view=" + view);
if (view != null) {
view.setVisibility(VISIBLE);
view.bringToFront();
view.requestLayout();
view.invalidate();
}
isVisible = true;
}
public static void hide() {
if (!isVisible) return;
SkipSegmentView view = SkipSegmentView.view.get();
if (VERBOSE)
Log.d(TAG, "hide; view=" + view);
if (view != null)
view.setVisibility(GONE);
isVisible = false;
}
public static void notifySkipped(SponsorSegment segment) {
if (segment == lastNotifiedSegment) {
if (VERBOSE)
Log.d(TAG, "notifySkipped; segment == lastNotifiedSegment");
return;
}
lastNotifiedSegment = segment;
String skipMessage = segment.category.skipMessage;
SkipSegmentView view = SkipSegmentView.view.get();
if (VERBOSE)
Log.d(TAG, String.format("notifySkipped; view=%s, message=%s", view, skipMessage));
if (view != null)
Toast.makeText(view.getContext(), skipMessage, Toast.LENGTH_SHORT).show();
}
public static float convertDpToPixel(float dp, Context context) {
return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT);
}
@Override
public void onClick(View v) {
PlayerController.onSkipSponsorClicked();
}
}

View File

@ -0,0 +1,249 @@
package pl.jakubweg;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.text.InputType;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import static pl.jakubweg.Helper.getStringByName;
import static pl.jakubweg.SponsorBlockSettings.DefaultBehaviour;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_CACHE_SEGMENTS;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_COUNT_SKIPS;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_NEW_SEGMENT_ENABLED;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_UUID;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_NAME;
import static pl.jakubweg.SponsorBlockSettings.adjustNewSegmentMillis;
import static pl.jakubweg.SponsorBlockSettings.cacheEnabled;
import static pl.jakubweg.SponsorBlockSettings.countSkips;
import static pl.jakubweg.SponsorBlockSettings.showToastWhenSkippedAutomatically;
import static pl.jakubweg.SponsorBlockSettings.uuid;
public class SponsorBlockPreferenceFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
private ArrayList<Preference> preferencesToDisableWhenSBDisabled = new ArrayList<>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getPreferenceManager().setSharedPreferencesName(PREFERENCES_NAME);
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
Activity context = this.getActivity();
PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(context);
setPreferenceScreen(preferenceScreen);
{
SwitchPreference preference = new SwitchPreference(context);
preferenceScreen.addPreference(preference);
preference.setKey(PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED);
preference.setDefaultValue(SponsorBlockSettings.isSponsorBlockEnabled);
preference.setChecked(SponsorBlockSettings.isSponsorBlockEnabled);
preference.setTitle(getStringByName(context, "enable_sb"));
preference.setSummary(getStringByName(context, "enable_sb_sum"));
preference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
enableCategoriesIfNeeded(((Boolean) newValue));
return true;
}
});
}
{
SwitchPreference preference = new SwitchPreference(context);
preferenceScreen.addPreference(preference);
preference.setKey(PREFERENCES_KEY_NEW_SEGMENT_ENABLED);
preference.setDefaultValue(SponsorBlockSettings.isAddNewSegmentEnabled);
preference.setChecked(SponsorBlockSettings.isAddNewSegmentEnabled);
preference.setTitle(getStringByName(context, "enable_segmadding"));
preference.setSummary(getStringByName(context, "enable_segmadding_sum"));
preferencesToDisableWhenSBDisabled.add(preference);
}
addGeneralCategory(context, preferenceScreen);
addSegmentsCategory(context, preferenceScreen);
addAboutCategory(context, preferenceScreen);
enableCategoriesIfNeeded(SponsorBlockSettings.isSponsorBlockEnabled);
}
private void enableCategoriesIfNeeded(boolean enabled) {
for (Preference preference : preferencesToDisableWhenSBDisabled)
preference.setEnabled(enabled);
}
@Override
public void onDestroy() {
super.onDestroy();
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
private void addSegmentsCategory(Context context, PreferenceScreen screen) {
PreferenceCategory category = new PreferenceCategory(context);
screen.addPreference(category);
preferencesToDisableWhenSBDisabled.add(category);
category.setTitle(getStringByName(context, "diff_segments"));
String defaultValue = DefaultBehaviour.key;
SponsorBlockSettings.SegmentBehaviour[] segmentBehaviours = SponsorBlockSettings.SegmentBehaviour.values();
String[] entries = new String[segmentBehaviours.length];
String[] entryValues = new String[segmentBehaviours.length];
for (int i = 0, segmentBehavioursLength = segmentBehaviours.length; i < segmentBehavioursLength; i++) {
SponsorBlockSettings.SegmentBehaviour behaviour = segmentBehaviours[i];
entries[i] = behaviour.name;
entryValues[i] = behaviour.key;
}
for (SponsorBlockSettings.SegmentInfo segmentInfo : SponsorBlockSettings.SegmentInfo.valuesWithoutPreview()) {
ListPreference preference = new ListPreference(context);
preference.setTitle(segmentInfo.getTitleWithDot());
preference.setSummary(segmentInfo.description);
preference.setKey(segmentInfo.key);
preference.setDefaultValue(defaultValue);
preference.setEntries(entries);
preference.setEntryValues(entryValues);
category.addPreference(preference);
}
}
private void addAboutCategory(Context context, PreferenceScreen screen) {
PreferenceCategory category = new PreferenceCategory(context);
screen.addPreference(category);
category.setTitle("About");
{
Preference preference = new Preference(context);
screen.addPreference(preference);
preference.setTitle(getStringByName(context, "about_api"));
preference.setSummary(getStringByName(context, "about_api_sum"));
preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("http://sponsor.ajay.app"));
preference.getContext().startActivity(i);
return false;
}
});
}
{
Preference preference = new Preference(context);
screen.addPreference(preference);
preference.setTitle(getStringByName(context, "about_madeby"));
}
}
private void addGeneralCategory(final Context context, PreferenceScreen screen) {
final PreferenceCategory category = new PreferenceCategory(context);
preferencesToDisableWhenSBDisabled.add(category);
screen.addPreference(category);
category.setTitle(getStringByName(context, "general"));
{
Preference preference = new SwitchPreference(context);
preference.setTitle(getStringByName(context, "general_skiptoast"));
preference.setSummary(getStringByName(context, "general_skiptoast_sum"));
preference.setKey(PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP);
preference.setDefaultValue(showToastWhenSkippedAutomatically);
preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Toast.makeText(preference.getContext(), getStringByName(context, "skipped_segment"), Toast.LENGTH_SHORT).show();
return false;
}
});
preferencesToDisableWhenSBDisabled.add(preference);
screen.addPreference(preference);
}
{
Preference preference = new SwitchPreference(context);
preference.setTitle(getStringByName(context, "general_skipcount"));
preference.setSummary(getStringByName(context, "general_skipcount_sum"));
preference.setKey(PREFERENCES_KEY_COUNT_SKIPS);
preference.setDefaultValue(countSkips);
preferencesToDisableWhenSBDisabled.add(preference);
screen.addPreference(preference);
}
{
EditTextPreference preference = new EditTextPreference(context);
preference.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
preference.setTitle(getStringByName(context, "general_adjusting"));
preference.setSummary(getStringByName(context, "general_adjusting_sum"));
preference.setKey(PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP);
preference.setDefaultValue(String.valueOf(adjustNewSegmentMillis));
screen.addPreference(preference);
preferencesToDisableWhenSBDisabled.add(preference);
}
{
Preference preference = new EditTextPreference(context);
preference.setTitle(getStringByName(context, "general_uuid"));
preference.setSummary(getStringByName(context, "general_uuid_sum"));
preference.setKey(PREFERENCES_KEY_UUID);
preference.setDefaultValue(uuid);
screen.addPreference(preference);
preferencesToDisableWhenSBDisabled.add(preference);
}
{
Preference preference = new SwitchPreference(context);
preference.setTitle(getStringByName(context, "general_cache"));
preference.setSummary(getStringByName(context, "general_cache_sum"));
preference.setKey(PREFERENCES_KEY_CACHE_SEGMENTS);
preference.setDefaultValue(cacheEnabled);
screen.addPreference(preference);
preferencesToDisableWhenSBDisabled.add(preference);
}
{
Preference preference = new Preference(context);
preference.setTitle(getStringByName(context, "general_cache_clear"));
preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
File cacheDirectory = SponsorBlockSettings.cacheDirectory;
if (cacheDirectory != null) {
for (File file : cacheDirectory.listFiles()) {
if (!file.delete())
return false;
}
Toast.makeText(getActivity(), getStringByName(context, "done"), Toast.LENGTH_SHORT).show();
}
return false;
}
});
preferencesToDisableWhenSBDisabled.add(preference);
screen.addPreference(preference);
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
SponsorBlockSettings.update(getActivity());
}
}

View File

@ -0,0 +1,215 @@
package pl.jakubweg;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import static pl.jakubweg.Helper.getStringByName;
public class SponsorBlockSettings {
public static final String CACHE_DIRECTORY_NAME = "sponsor-block-segments-1";
public static final String PREFERENCES_NAME = "sponsor-block";
public static final String PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP = "show-toast";
public static final String PREFERENCES_KEY_COUNT_SKIPS = "count-skips";
public static final String PREFERENCES_KEY_UUID = "uuid";
public static final String PREFERENCES_KEY_CACHE_SEGMENTS = "cache-enabled";
public static final String PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP = "new-segment-step-accuracy";
public static final String PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED = "sb-enabled";
public static final String PREFERENCES_KEY_NEW_SEGMENT_ENABLED = "sb-new-segment-enabled";
public static final String sponsorBlockSkipSegmentsUrl = "https://sponsor.ajay.app/api/skipSegments";
public static final String sponsorBlockViewedUrl = "https://sponsor.ajay.app/api/viewedVideoSponsorTime";
public static final SegmentBehaviour DefaultBehaviour = SegmentBehaviour.SkipAutomatically;
public static boolean isSponsorBlockEnabled = false;
public static boolean isAddNewSegmentEnabled = false;
public static boolean showToastWhenSkippedAutomatically = true;
public static boolean countSkips = true;
public static boolean cacheEnabled = true;
public static int adjustNewSegmentMillis = 150;
public static String uuid = "<invalid>";
public static File cacheDirectory;
static Context context;
private static String sponsorBlockUrlCategories = "[]";
public SponsorBlockSettings(Context context) {
SponsorBlockSettings.context = context;
}
public static String getSponsorBlockUrlWithCategories(String videoId) {
return sponsorBlockSkipSegmentsUrl + "?videoID=" + videoId + "&categories=" + sponsorBlockUrlCategories;
}
public static String getSponsorBlockViewedUrl(String UUID) {
return sponsorBlockViewedUrl + "?UUID=" + UUID;
}
public static void update(Context context) {
if (context == null) return;
File directory = cacheDirectory = new File(context.getCacheDir(), CACHE_DIRECTORY_NAME);
if (!directory.mkdirs() && !directory.exists()) {
Log.e("jakubweg.Settings", "Unable to create cache directory");
cacheDirectory = null;
}
SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
isSponsorBlockEnabled = preferences.getBoolean(PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED, isSponsorBlockEnabled);
if (!isSponsorBlockEnabled) {
SkipSegmentView.hide();
NewSegmentHelperLayout.hide();
SponsorBlockUtils.hideButton();
PlayerController.sponsorSegmentsOfCurrentVideo = null;
} else if (isAddNewSegmentEnabled) {
SponsorBlockUtils.showButton();
}
isAddNewSegmentEnabled = preferences.getBoolean(PREFERENCES_KEY_NEW_SEGMENT_ENABLED, isAddNewSegmentEnabled);
if (!isAddNewSegmentEnabled) {
NewSegmentHelperLayout.hide();
SponsorBlockUtils.hideButton();
} else {
SponsorBlockUtils.showButton();
}
SegmentBehaviour[] possibleBehaviours = SegmentBehaviour.values();
final ArrayList<String> enabledCategories = new ArrayList<>(possibleBehaviours.length);
for (SegmentInfo segment : SegmentInfo.valuesWithoutPreview()) {
SegmentBehaviour behaviour = null;
String value = preferences.getString(segment.key, null);
if (value == null)
behaviour = DefaultBehaviour;
else {
for (SegmentBehaviour possibleBehaviour : possibleBehaviours) {
if (possibleBehaviour.key.equals(value)) {
behaviour = possibleBehaviour;
break;
}
}
}
if (behaviour == null)
behaviour = DefaultBehaviour;
segment.behaviour = behaviour;
if (behaviour.showOnTimeBar)
enabledCategories.add(segment.key);
}
//"[%22sponsor%22,%22outro%22,%22music_offtopic%22,%22intro%22,%22selfpromo%22,%22interaction%22]";
if (enabledCategories.size() == 0)
sponsorBlockUrlCategories = "[]";
else
sponsorBlockUrlCategories = "[%22" + TextUtils.join("%22,%22", enabledCategories) + "%22]";
showToastWhenSkippedAutomatically = preferences.getBoolean(PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP, showToastWhenSkippedAutomatically);
cacheEnabled = preferences.getBoolean(PREFERENCES_KEY_CACHE_SEGMENTS, true);
adjustNewSegmentMillis = Integer.parseInt(preferences
.getString(PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP,
String.valueOf(adjustNewSegmentMillis)));
uuid = preferences.getString(PREFERENCES_KEY_UUID, null);
if (uuid == null) {
uuid = (UUID.randomUUID().toString() +
UUID.randomUUID().toString() +
UUID.randomUUID().toString())
.replace("-", "");
preferences.edit().putString(PREFERENCES_KEY_UUID, uuid).apply();
}
}
public enum SegmentBehaviour {
SkipAutomatically("skip", getStringByName(context, "skip_automatically"), true, true),
ManualSkip("manual-skip", getStringByName(context, "skip_showbutton"), false, true),
Ignore("ignore", getStringByName(context, "skip_ignore"), false, false);
public final String key;
public final String name;
public final boolean skip;
public final boolean showOnTimeBar;
SegmentBehaviour(String key,
String name,
boolean skip,
boolean showOnTimeBar) {
this.key = key;
this.name = name;
this.skip = skip;
this.showOnTimeBar = showOnTimeBar;
}
}
public enum SegmentInfo {
Sponsor("sponsor", getStringByName(context, "segments_sponsor"), getStringByName(context, "skipped_sponsor"), getStringByName(context, "segments_sponsor_sum"), null, 0xFF00d400),
Intro("intro", getStringByName(context, "segments_intermission"), getStringByName(context, "skipped_intermission"), getStringByName(context, "segments_intermission_sum"), null, 0xFF00ffff),
Outro("outro", getStringByName(context, "segments_endcard"), getStringByName(context, "skipped_endcard"), getStringByName(context, "segments_endcards_sum"), null, 0xFF0202ed),
Interaction("interaction", getStringByName(context, "segments_subscribe"), getStringByName(context, "skipped_subscribe"), getStringByName(context, "segments_subscribe_sum"), null, 0xFFcc00ff),
SelfPromo("selfpromo", getStringByName(context, "segments_selfpromo"), getStringByName(context, "skipped_selfpromo"), getStringByName(context, "segments_selfpromo_sum"), null, 0xFFffff00),
MusicOfftopic("music_offtopic", getStringByName(context, "segments_music"), getStringByName(context, "skipped_music"), getStringByName(context, "segments_music_sum"), null, 0xFFff9900),
Preview("preview", "", getStringByName(context, "skipped_preview"), "", SegmentBehaviour.SkipAutomatically, 0xFF000000),
;
private static SegmentInfo[] mValuesWithoutPreview = new SegmentInfo[]{
Sponsor,
Intro,
Outro,
Interaction,
SelfPromo,
MusicOfftopic
};
private static Map<String, SegmentInfo> mValuesMap = new HashMap<>(7);
static {
for (SegmentInfo value : valuesWithoutPreview())
mValuesMap.put(value.key, value);
}
public final String key;
public final String title;
public final String skipMessage;
public final String description;
public final int color;
public final Paint paint;
public SegmentBehaviour behaviour;
private CharSequence lazyTitleWithDot;
SegmentInfo(String key,
String title,
String skipMessage,
String description,
SegmentBehaviour behaviour,
int color) {
this.key = key;
this.title = title;
this.skipMessage = skipMessage;
this.description = description;
this.behaviour = behaviour;
this.color = color & 0xFFFFFF;
paint = new Paint();
paint.setColor(color);
}
public static SegmentInfo[] valuesWithoutPreview() {
return mValuesWithoutPreview;
}
public static SegmentInfo byCategoryKey(String key) {
return mValuesMap.get(key);
}
public CharSequence getTitleWithDot() {
return (lazyTitleWithDot == null) ?
lazyTitleWithDot = Html.fromHtml(String.format("<font color=\"#%06X\">⬤</font> %s", color, title))
: lazyTitleWithDot;
}
}
}

View File

@ -0,0 +1,648 @@
package pl.jakubweg;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;
import java.util.TimeZone;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static pl.jakubweg.PlayerController.VERBOSE;
import static pl.jakubweg.PlayerController.getCurrentVideoId;
import static pl.jakubweg.PlayerController.getLastKnownVideoTime;
import static pl.jakubweg.PlayerController.sponsorSegmentsOfCurrentVideo;
import static pl.jakubweg.SponsorBlockSettings.sponsorBlockSkipSegmentsUrl;
@SuppressWarnings({"LongLogTag"})
public abstract class SponsorBlockUtils {
public static final String TAG = "jakubweg.SponsorBlockUtils";
public static final String DATE_FORMAT = "HH:mm:ss.SSS";
@SuppressLint("SimpleDateFormat")
public static final SimpleDateFormat dateFormatter = new SimpleDateFormat(DATE_FORMAT);
private static final int sponsorBtnId = 1234;
private static final View.OnClickListener sponsorBlockBtnListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
NewSegmentHelperLayout.toggle();
}
};
private static int shareBtnId = -1;
private static long newSponsorSegmentDialogShownMillis;
private static long newSponsorSegmentStartMillis = -1;
private static long newSponsorSegmentEndMillis = -1;
private static final DialogInterface.OnClickListener newSponsorSegmentDialogListener = new DialogInterface.OnClickListener() {
@SuppressLint("DefaultLocale")
@Override
public void onClick(DialogInterface dialog, int which) {
Context context = ((AlertDialog) dialog).getContext();
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
// start
newSponsorSegmentStartMillis = newSponsorSegmentDialogShownMillis;
Toast.makeText(context.getApplicationContext(), "Start of the segment set", Toast.LENGTH_LONG).show();
break;
case DialogInterface.BUTTON_POSITIVE:
// end
newSponsorSegmentEndMillis = newSponsorSegmentDialogShownMillis;
Toast.makeText(context.getApplicationContext(), "End of the segment set", Toast.LENGTH_SHORT).show();
break;
}
dialog.dismiss();
}
};
private static SponsorBlockSettings.SegmentInfo newSponsorBlockSegmentType;
private static final DialogInterface.OnClickListener segmentTypeListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SponsorBlockSettings.SegmentInfo segmentType = SponsorBlockSettings.SegmentInfo.valuesWithoutPreview()[which];
boolean enableButton;
if (!segmentType.behaviour.showOnTimeBar) {
Toast.makeText(
((AlertDialog) dialog).getContext().getApplicationContext(),
"You've disabled this category in the settings, so can't submit it",
Toast.LENGTH_SHORT).show();
enableButton = false;
} else {
Toast.makeText(
((AlertDialog) dialog).getContext().getApplicationContext(),
segmentType.description,
Toast.LENGTH_SHORT).show();
newSponsorBlockSegmentType = segmentType;
enableButton = true;
}
((AlertDialog) dialog)
.getButton(DialogInterface.BUTTON_POSITIVE)
.setEnabled(enableButton);
}
};
private static final DialogInterface.OnClickListener segmentReadyDialogButtonListener = new DialogInterface.OnClickListener() {
@SuppressLint("DefaultLocale")
@Override
public void onClick(DialogInterface dialog, int which) {
NewSegmentHelperLayout.hide();
Context context = ((AlertDialog) dialog).getContext();
dialog.dismiss();
SponsorBlockSettings.SegmentInfo[] values = SponsorBlockSettings.SegmentInfo.valuesWithoutPreview();
CharSequence[] titles = new CharSequence[values.length];
for (int i = 0; i < values.length; i++) {
// titles[i] = values[i].title;
titles[i] = values[i].getTitleWithDot();
}
newSponsorBlockSegmentType = null;
new AlertDialog.Builder(context)
.setTitle("Choose the segment category")
.setSingleChoiceItems(titles, -1, segmentTypeListener)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, segmentCategorySelectedDialogListener)
.show()
.getButton(DialogInterface.BUTTON_POSITIVE)
.setEnabled(false);
}
};
private static WeakReference<Context> appContext = new WeakReference<>(null);
private static final DialogInterface.OnClickListener segmentCategorySelectedDialogListener = new DialogInterface.OnClickListener() {
@SuppressLint("DefaultLocale")
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
Context context = ((AlertDialog) dialog).getContext().getApplicationContext();
Toast.makeText(context, "Submitting segment...", Toast.LENGTH_SHORT).show();
appContext = new WeakReference<>(context);
new Thread(submitRunnable).start();
}
};
private static boolean isShown = false;
private static WeakReference<ImageView> sponsorBlockBtn = new WeakReference<>(null);
private static String messageToToast = "";
private static EditByHandSaveDialogListener editByHandSaveDialogListener = new EditByHandSaveDialogListener();
private static final DialogInterface.OnClickListener editByHandDialogListener = new DialogInterface.OnClickListener() {
@SuppressLint("DefaultLocale")
@Override
public void onClick(DialogInterface dialog, int which) {
Context context = ((AlertDialog) dialog).getContext();
final boolean isStart = DialogInterface.BUTTON_NEGATIVE == which;
final EditText textView = new EditText(context);
textView.setHint(DATE_FORMAT);
if (isStart) {
if (newSponsorSegmentStartMillis >= 0)
textView.setText(dateFormatter.format(new Date(newSponsorSegmentStartMillis)));
} else {
if (newSponsorSegmentEndMillis >= 0)
textView.setText(dateFormatter.format(new Date(newSponsorSegmentEndMillis)));
}
editByHandSaveDialogListener.settingStart = isStart;
editByHandSaveDialogListener.editText = new WeakReference<>(textView);
new AlertDialog.Builder(context)
.setTitle("Time of the " + (isStart ? "start" : "end") + " of the segment")
.setView(textView)
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton("now", editByHandSaveDialogListener)
.setPositiveButton(android.R.string.ok, editByHandSaveDialogListener)
.show();
dialog.dismiss();
}
};
private static Runnable toastRunnable = new Runnable() {
@Override
public void run() {
Context context = appContext.get();
if (context != null && messageToToast != null)
Toast.makeText(context, messageToToast, Toast.LENGTH_LONG).show();
}
};
private static final Runnable submitRunnable = new Runnable() {
@Override
public void run() {
messageToToast = null;
final String uuid = SponsorBlockSettings.uuid;
final long start = newSponsorSegmentStartMillis;
final long end = newSponsorSegmentEndMillis;
final String videoId = getCurrentVideoId();
final SponsorBlockSettings.SegmentInfo segmentType = SponsorBlockUtils.newSponsorBlockSegmentType;
try {
if (start < 0 || end < 0 || start >= end || segmentType == null || videoId == null || uuid == null) {
Log.e(TAG, "Unable to submit times, invalid parameters");
return;
}
URL url = new URL(String.format(Locale.US,
sponsorBlockSkipSegmentsUrl + "?videoID=%s&userID=%s&startTime=%.3f&endTime=%.3f&category=%s",
videoId, uuid, ((float) start) / 1000f, ((float) end) / 1000f, segmentType.key));
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
switch (connection.getResponseCode()) {
default:
messageToToast = "Unable to submit segments: Status: " + connection.getResponseCode() + " " + connection.getResponseMessage();
break;
case 429:
messageToToast = "Can't submit the segment.\nRate Limit (Too many for the same user or IP)";
break;
case 403:
messageToToast = "Can't submit the segment.\nRejected by auto moderator";
break;
case 409:
messageToToast = "Duplicate";
break;
case 200:
messageToToast = "Segment submitted successfully";
break;
}
Log.i(TAG, "Segment submitted with status: " + connection.getResponseCode() + ", " + messageToToast);
new Handler(Looper.getMainLooper()).post(toastRunnable);
connection.disconnect();
newSponsorSegmentEndMillis = newSponsorSegmentStartMillis = -1;
} catch (Exception e) {
Log.e(TAG, "Unable to submit segment", e);
}
if (videoId != null)
PlayerController.executeDownloadSegments(videoId, true);
}
};
static {
dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private SponsorBlockUtils() {
}
public static void showButton() {
if (isShown) return;
isShown = true;
View i = sponsorBlockBtn.get();
if (i == null) return;
i.setVisibility(VISIBLE);
i.bringToFront();
i.requestLayout();
i.invalidate();
}
public static void hideButton() {
if (!isShown) return;
isShown = false;
View i = sponsorBlockBtn.get();
if (i != null)
i.setVisibility(GONE);
}
@SuppressLint("LongLogTag")
public static void addImageButton(final Activity activity, final int attemptsWhenFail) {
if (VERBOSE)
Log.d(TAG, "addImageButton activity=" + activity + ",attemptsWhenFail=" + attemptsWhenFail);
if (activity == null)
return;
final View existingSponsorBtn = activity.findViewById(sponsorBtnId);
if (existingSponsorBtn != null) {
if (VERBOSE)
Log.d(TAG, "addImageButton: sponsorBtn exists");
if (SponsorBlockSettings.isAddNewSegmentEnabled)
showButton();
return;
}
String packageName = activity.getPackageName();
Resources R = activity.getResources();
shareBtnId = R.getIdentifier("player_share_button", "id", packageName);
// final int addToBtnId = R.getIdentifier("player_addto_button", "id", packageName);
final int addToBtnId = R.getIdentifier("live_chat_overlay_button", "id", packageName);
int titleViewId = R.getIdentifier("player_video_title_view", "id", packageName);
// final int iconId = R.getIdentifier("player_fast_forward", "drawable", packageName);
final int iconId = R.getIdentifier("ic_sb_logo", "drawable", packageName);
final View addToBtn = activity.findViewById(addToBtnId);
final ImageView shareBtn = activity.findViewById(shareBtnId);
final TextView titleView = activity.findViewById(titleViewId);
if (addToBtn == null || shareBtn == null || titleView == null) {
if (VERBOSE)
Log.e(TAG, String.format("one of following is null: addToBtn=%s shareBtn=%s titleView=%s",
addToBtn, shareBtn, titleView));
if (attemptsWhenFail > 0)
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
if (VERBOSE)
Log.i(TAG, "Retrying addImageButton");
addImageButton(PlayerController.playerActivity.get(), attemptsWhenFail - 1);
}
}, 5000);
return;
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
try {
Class<?> touchImageViewClass = Class.forName("com.google.android.libraries.youtube.common.ui.TouchImageView");
Constructor<?> constructor = touchImageViewClass.getConstructor(Context.class);
final ImageView instance = ((ImageView) constructor.newInstance(activity));
instance.setImageResource(iconId);
instance.setId(sponsorBtnId);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(shareBtn.getLayoutParams());
layoutParams.addRule(RelativeLayout.LEFT_OF, addToBtnId);
instance.setLayoutParams(layoutParams);
((ViewGroup) shareBtn.getParent()).addView(instance, 0);
instance.setPadding(shareBtn.getPaddingLeft(),
shareBtn.getPaddingTop(),
shareBtn.getPaddingRight(),
shareBtn.getPaddingBottom());
RelativeLayout.LayoutParams titleViewLayoutParams = (RelativeLayout.LayoutParams) titleView.getLayoutParams();
titleViewLayoutParams.addRule(RelativeLayout.START_OF, sponsorBtnId);
titleView.requestLayout();
instance.setClickable(true);
instance.setFocusable(true);
Drawable.ConstantState constantState = shareBtn.getBackground().mutate().getConstantState();
if (constantState != null)
instance.setBackground(constantState.newDrawable());
instance.setOnClickListener(sponsorBlockBtnListener);
sponsorBlockBtn = new WeakReference<>(instance);
isShown = true;
if (!SponsorBlockSettings.isAddNewSegmentEnabled)
hideButton();
if (VERBOSE)
Log.i(TAG, "Image Button added");
} catch (Exception e) {
Log.e(TAG, "Error while adding button", e);
}
}
});
}
@SuppressLint("DefaultLocale")
public static void onMarkLocationClicked(Context context) {
newSponsorSegmentDialogShownMillis = PlayerController.getLastKnownVideoTime();
new AlertDialog.Builder(context)
.setTitle("New Sponsor Block segment")
.setMessage(String.format("Set %02d:%02d:%04d as a start or end of new segment?",
newSponsorSegmentDialogShownMillis / 60000,
newSponsorSegmentDialogShownMillis / 1000 % 60,
newSponsorSegmentDialogShownMillis % 1000))
.setNeutralButton("Cancel", null)
.setNegativeButton("Start", newSponsorSegmentDialogListener)
.setPositiveButton("End", newSponsorSegmentDialogListener)
.show();
}
@SuppressLint("DefaultLocale")
public static void onPublishClicked(Context context) {
if (newSponsorSegmentStartMillis >= 0 && newSponsorSegmentStartMillis < newSponsorSegmentEndMillis) {
long length = (newSponsorSegmentEndMillis - newSponsorSegmentStartMillis) / 1000;
long start = (newSponsorSegmentStartMillis) / 1000;
long end = (newSponsorSegmentEndMillis) / 1000;
new AlertDialog.Builder(context)
.setTitle("Is it right?")
.setMessage(String.format("The segment lasts from %02d:%02d to %02d:%02d (%d minutes %02d seconds)\nIs it ready to submit?",
start / 60, start % 60,
end / 60, end % 60,
length / 60, length % 60))
.setNegativeButton(android.R.string.no, null)
.setPositiveButton(android.R.string.yes, segmentReadyDialogButtonListener)
.show();
} else {
Toast.makeText(context, "Mark two locations on the time bar first", Toast.LENGTH_SHORT).show();
}
}
@SuppressLint("DefaultLocale")
public static void onPreviewClicked(Context context) {
if (newSponsorSegmentStartMillis >= 0 && newSponsorSegmentStartMillis < newSponsorSegmentEndMillis) {
Toast t = Toast.makeText(context, "Preview", Toast.LENGTH_SHORT);
t.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP, t.getXOffset(), t.getYOffset());
t.show();
PlayerController.skipToMillisecond(newSponsorSegmentStartMillis - 3000);
final SponsorSegment[] original = PlayerController.sponsorSegmentsOfCurrentVideo;
final SponsorSegment[] segments = original == null ? new SponsorSegment[1] : Arrays.copyOf(original, original.length + 1);
segments[segments.length - 1] = new SponsorSegment(newSponsorSegmentStartMillis, newSponsorSegmentEndMillis,
SponsorBlockSettings.SegmentInfo.Preview, null);
Arrays.sort(segments);
sponsorSegmentsOfCurrentVideo = segments;
} else {
Toast.makeText(context, "Mark two locations on the time bar first", Toast.LENGTH_SHORT).show();
}
}
@SuppressLint("DefaultLocale")
public static void onEditByHandClicked(Context context) {
new AlertDialog.Builder(context)
.setTitle("Edit time of new segment by hand")
.setMessage("Do you want to edit time of the start or the end of the segment?")
.setNeutralButton(android.R.string.cancel, null)
.setNegativeButton("start", editByHandDialogListener)
.setPositiveButton("end", editByHandDialogListener)
.show();
}
public static void notifyShareBtnVisibilityChanged(View v) {
if (v.getId() != shareBtnId || !SponsorBlockSettings.isAddNewSegmentEnabled) return;
// if (VERBOSE)
// Log.d(TAG, "VISIBILITY CHANGED of view " + v);
ImageView sponsorBtn = sponsorBlockBtn.get();
if (sponsorBtn != null) {
sponsorBtn.setVisibility(v.getVisibility());
}
}
public synchronized static SponsorSegment[] getSegmentsForVideo(String videoId, boolean ignoreCache) {
newSponsorSegmentEndMillis = newSponsorSegmentStartMillis = -1;
int usageCounter = 0;
if (!ignoreCache && SponsorBlockSettings.cacheEnabled) {
File cacheDirectory = SponsorBlockSettings.cacheDirectory;
if (cacheDirectory == null) {
Log.w(TAG, "Cache directory is null, cannot read");
} else {
File file = new File(cacheDirectory, videoId);
try {
RandomAccessFile rwd = new RandomAccessFile(file, "rw");
rwd.seek(0);
usageCounter = rwd.readInt();
long now = System.currentTimeMillis();
long savedTimestamp = rwd.readLong();
int segmentsSize = rwd.readInt();
byte maxDaysCache;
if (usageCounter < 2)
maxDaysCache = 0;
else if (usageCounter < 5 || segmentsSize == 0)
maxDaysCache = 2;
else if (usageCounter < 10)
maxDaysCache = 5;
else
maxDaysCache = 10;
if (VERBOSE)
Log.d(TAG, String.format("Read cache data about segments, counter=%d, timestamp=%d, now=%d, maxCacheDays=%s, segmentsSize=%d",
usageCounter, savedTimestamp, now, maxDaysCache, segmentsSize));
if (savedTimestamp + (((long) maxDaysCache) * 24 * 60 * 60 * 1000) > now) {
if (VERBOSE)
Log.d(TAG, "getSegmentsForVideo: cacheHonored videoId=" + videoId);
SponsorSegment[] segments = new SponsorSegment[segmentsSize];
for (int i = 0; i < segmentsSize; i++) {
segments[i] = SponsorSegment.readFrom(rwd);
}
rwd.seek(0);
rwd.writeInt(usageCounter + 1);
rwd.close();
if (VERBOSE)
Log.d(TAG, "getSegmentsForVideo: reading from cache and updating usageCounter finished");
return segments;
} else {
if (VERBOSE)
Log.d(TAG, "getSegmentsForVideo: cache of video " + videoId + " was not honored, fallback to downloading...");
}
} catch (FileNotFoundException | EOFException ignored) {
if (VERBOSE)
Log.e(TAG, "FileNotFoundException | EOFException ignored");
} catch (Exception e) {
//noinspection ResultOfMethodCallIgnored
file.delete();
Log.e(TAG, "Error while reading cached segments", e);
}
}
}
ArrayList<SponsorSegment> sponsorSegments = new ArrayList<>();
try {
if (VERBOSE)
Log.i(TAG, "Trying to download segments for videoId=" + videoId);
URL url = new URL(SponsorBlockSettings.getSponsorBlockUrlWithCategories(videoId));
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
switch (connection.getResponseCode()) {
default:
Log.e(TAG, "Unable to download segments: Status: " + connection.getResponseCode() + " " + connection.getResponseMessage());
break;
case 404:
Log.w(TAG, "No segments for this video (ERR404)");
break;
case 200:
if (VERBOSE)
Log.i(TAG, "Received status 200 OK, parsing response...");
StringBuilder stringBuilder = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
connection.getInputStream().close();
JSONArray responseArray = new JSONArray(stringBuilder.toString());
int length = responseArray.length();
for (int i = 0; i < length; i++) {
JSONObject obj = ((JSONObject) responseArray.get(i));
JSONArray segments = obj.getJSONArray("segment");
long start = (long) (segments.getDouble(0) * 1000);
long end = (long) (segments.getDouble(1) * 1000);
String category = obj.getString("category");
String UUID = obj.getString("UUID");
SponsorBlockSettings.SegmentInfo segmentCategory = SponsorBlockSettings.SegmentInfo.byCategoryKey(category);
if (segmentCategory != null && segmentCategory.behaviour.showOnTimeBar) {
SponsorSegment segment = new SponsorSegment(start, end, segmentCategory, UUID);
sponsorSegments.add(segment);
}
}
if (VERBOSE)
Log.v(TAG, "Parsing done");
break;
}
connection.disconnect();
if (SponsorBlockSettings.cacheEnabled) {
File cacheDirectory = SponsorBlockSettings.cacheDirectory;
if (cacheDirectory == null) {
Log.w(TAG, "Cache directory is null");
} else {
File file = new File(cacheDirectory, videoId);
try {
DataOutputStream stream = new DataOutputStream(new FileOutputStream(file));
stream.writeInt(usageCounter + 1);
stream.writeLong(System.currentTimeMillis());
stream.writeInt(sponsorSegments.size());
for (SponsorSegment segment : sponsorSegments) {
segment.writeTo(stream);
}
stream.close();
} catch (Exception e) {
//noinspection ResultOfMethodCallIgnored
file.delete();
Log.e(TAG, "Unable to write segments to file", e);
}
}
}
} catch (Exception e) {
Log.e(TAG, "download segments failed", e);
}
return sponsorSegments.toArray(new SponsorSegment[0]);
}
public static void sendViewCountRequest(SponsorSegment segment) {
try {
URL url = new URL(SponsorBlockSettings.getSponsorBlockViewedUrl(segment.UUID));
Log.d("sponsorblock", "requesting: " + url.getPath());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.getInputStream().close();
connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
private static class EditByHandSaveDialogListener implements DialogInterface.OnClickListener {
public boolean settingStart;
public WeakReference<EditText> editText;
@SuppressLint("DefaultLocale")
@Override
public void onClick(DialogInterface dialog, int which) {
final EditText editText = this.editText.get();
if (editText == null) return;
Context context = ((AlertDialog) dialog).getContext();
try {
long time = (which == DialogInterface.BUTTON_NEUTRAL) ?
getLastKnownVideoTime() :
(Objects.requireNonNull(dateFormatter.parse(editText.getText().toString())).getTime());
if (settingStart)
newSponsorSegmentStartMillis = Math.max(time, 0);
else
newSponsorSegmentEndMillis = time;
if (which == DialogInterface.BUTTON_NEUTRAL)
editByHandDialogListener.onClick(dialog, settingStart ?
DialogInterface.BUTTON_NEGATIVE :
DialogInterface.BUTTON_POSITIVE);
else
Toast.makeText(context.getApplicationContext(), "Done", Toast.LENGTH_SHORT).show();
} catch (ParseException e) {
Toast.makeText(context.getApplicationContext(), "Cannot parse this time 😔", Toast.LENGTH_LONG).show();
}
}
}
}

View File

@ -0,0 +1,49 @@
package pl.jakubweg;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
public class SponsorSegment implements Comparable<SponsorSegment> {
public final long start;
public final long end;
public final SponsorBlockSettings.SegmentInfo category;
public final String UUID;
public SponsorSegment(long start, long end, SponsorBlockSettings.SegmentInfo category, String UUID) {
this.start = start;
this.end = end;
this.category = category;
this.UUID = UUID;
}
public static SponsorSegment readFrom(RandomAccessFile stream) throws IOException {
long start = stream.readLong();
long end = stream.readLong();
String categoryName = stream.readUTF();
String UUID = stream.readUTF();
SponsorBlockSettings.SegmentInfo category = SponsorBlockSettings.SegmentInfo.valueOf(categoryName);
return new SponsorSegment(start, end, category, UUID);
}
@Override
public String toString() {
return "SegmentInfo{" +
"start=" + start +
", end=" + end +
", category='" + category + '\'' +
'}';
}
@Override
public int compareTo(SponsorSegment o) {
return (int) (this.start - o.start);
}
public void writeTo(DataOutputStream stream) throws IOException {
stream.writeLong(start);
stream.writeLong(end);
stream.writeUTF(category.name());
stream.writeUTF(UUID);
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M12,2C6.49,2 2,6.49 2,12s4.49,10 10,10 10,-4.49 10,-10S17.51,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM15,12c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3 1.34,-3 3,-3 3,1.34 3,3z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M10,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h5v2h2L12,1h-2v2zM10,18L5,18l5,-6v6zM19,3h-5v2h5v13l-5,-6v9h5c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
</vector>

View File

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:pivotX="12"
android:pivotY="12"
android:scaleX=".8"
android:scaleY=".8">
<path
android:fillColor="#FFFFFF"
android:pathData="M8,6.82v10.36c0,0.79 0.87,1.27 1.54,0.84l8.14,-5.18c0.62,-0.39 0.62,-1.29 0,-1.69L9.54,5.98C8.87,5.55 8,6.03 8,6.82z" />
</group>
<path
android:fillColor="#FFFFFF"
android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12L21,5l-9,-4zM19,11c0,4.52 -2.98,8.69 -7,9.93 -4.02,-1.24 -7,-5.41 -7,-9.93L5,6.3l7,-3.11 7,3.11L19,14.17z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M5,5c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L6,4c-0.55,0 -1,0.45 -1,1zM7.41,14L9,14v5c0,0.55 0.45,1 1,1h4c0.55,0 1,-0.45 1,-1v-5h1.59c0.89,0 1.34,-1.08 0.71,-1.71L12.71,7.7c-0.39,-0.39 -1.02,-0.39 -1.41,0l-4.59,4.59c-0.63,0.63 -0.19,1.71 0.7,1.71z" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,180 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="xfile_about_summary">"
- xfileFIN (Mods, Theming, Support)
- Laura (Theming, Support)
- ZaneZam (Publishing, Support)
- KevinX8 (Neko, Support)"</string>
<string name="xfile_about_title">About</string>
<string name="xfile_auto_repeat_bg_summary_off">Auto repeat in background is off</string>
<string name="xfile_auto_repeat_bg_summary_on">Auto repeat in background is on</string>
<string name="xfile_auto_repeat_bg_title">Auto repeat in background</string>
<string name="xfile_auto_repeat_linked_summary_off">Auto repeat is not linked to Autoplay toggle</string>
<string name="xfile_auto_repeat_linked_summary_on">Auto repeat is linked to Autoplay toggle (Autoplay off = Auto repeat on)</string>
<string name="xfile_auto_repeat_linked_title">Auto repeat linked to Autoplay</string>
<string name="xfile_auto_repeat_summary_off">Auto repeat is off</string>
<string name="xfile_auto_repeat_summary_on">Auto repeat is on</string>
<string name="xfile_auto_repeat_title">Auto repeat</string>
<string name="xfile_branding_watermark_summary_off">Video watermark is hidden</string>
<string name="xfile_branding_watermark_summary_on">Video watermark is shown</string>
<string name="xfile_branding_watermark_title">Video watermark</string>
<string name="xfile_buffer_summary">ExoPlayer v2 has to be enabled for buffer settings</string>
<string name="xfile_buffer_title">Buffer settings</string>
<string name="xfile_cast_button_summary_off">Cast button is hidden</string>
<string name="xfile_cast_button_summary_on">Cast button is shown</string>
<string name="xfile_cast_button_title">Cast button</string>
<string name="xfile_codec_override_title">Codec override</string>
<string name="xfile_current_override_manufacturer">Overrided manufacturer</string>
<string name="xfile_current_override_model">Overrided model</string>
<string name="xfile_debug_summary_off">Extra debug logging is disabled</string>
<string name="xfile_debug_summary_on">Extra debug logging is enabled</string>
<string name="xfile_debug_title">Debug mode</string>
<string name="xfile_default_codec_summary">Tap to set your device\'s default codec</string>
<string name="xfile_default_codec_title">Default codec</string>
<string name="xfile_discord_summary">Tap to join Vanced on Discord</string>
<string name="xfile_discord_title">Discord Server</string>
<string name="xfile_exoplayerv2_warning_summary">ExoPlayer v2 is experimental. DO NOT report errors happened when ExoPlayer v2 is enabled</string>
<string name="xfile_exoplayerv2_warning_title">Warning</string>
<string name="xfile_hardware_hdr_summary">Tap to enable hardware HDR</string>
<string name="xfile_hardware_hdr_title" translatable="false">Samsung Galaxy S8+</string>
<string name="xfile_hdr_full_brightness_summary_off">Video brightness will follow your device\'s brightness on HDR landscape videos</string>
<string name="xfile_hdr_full_brightness_summary_on">Video brightness is set to max on HDR landscape videos</string>
<string name="xfile_hdr_full_brightness_title">HDR Max brightness</string>
<string name="xfile_hiddenmenu_needed">taps needed to enable hidden setting</string>
<string name="xfile_hiddenmenu_open">No need, hidden setting has already been enabled</string>
<string name="xfile_hiddenmenu_opened">Hidden setting has been enabled</string>
<string name="xfile_info_cards_summary_off">Info cards are hidden</string>
<string name="xfile_info_cards_summary_on">Info cards are shown</string>
<string name="xfile_info_cards_title">Info cards</string>
<string name="xfile_layout_settings_title">Layout settings</string>
<string name="xfile_maximum_buffer_summary">"The maximum duration of media that the player will attempt to buffer (in milliseconds)
Default: 120000"</string>
<string name="xfile_maximum_buffer_title">Maximum buffer</string>
<string name="xfile_minimized_video_type_summary">Select the preferred minimized video type</string>
<string name="xfile_minimized_video_type_title">Minimized video type</string>
<string name="xfile_miniplayer_style_video">Video only</string>
<string name="xfile_miniplayer_style_video_controls">Video with media controls</string>
<string name="xfile_misc_title">Misc</string>
<string name="xfile_override_resolution_summary_off">Video resolution is being overridden to max</string>
<string name="xfile_override_resolution_summary_on">Video resolution is following your device screen resolution</string>
<string name="xfile_override_resolution_title">Max resolution</string>
<string name="xfile_playback_start_summary">"The duration of media that must be buffered for playback to start or resume following a user action such as seeking (in milliseconds)
Default: 2500"</string>
<string name="xfile_playback_start_title">Playback start</string>
<string name="xfile_preferred_video_quality_mobile_summary">Select preferred video resolution on Cellular Network</string>
<string name="xfile_preferred_video_quality_mobile_title">Preferred video quality Cellular</string>
<string name="xfile_preferred_video_quality_wifi_summary">Select preferred video resolution on Wi-Fi Network</string>
<string name="xfile_preferred_video_quality_wifi_title">Preferred video quality Wi-Fi</string>
<string name="xfile_preferred_video_speed_summary">Select preferred video speed</string>
<string name="xfile_preferred_video_speed_title">Preferred video speed</string>
<string name="xfile_rebuffer_summary">"The duration of media that must be buffered for playback to resume after a rebuffer (in milliseconds). A rebuffer is defined to be caused by buffer depletion rather than a user action
Default: 5000"</string>
<string name="xfile_rebuffer_title">Rebuffer</string>
<string name="xfile_settings">Vanced settings</string>
<string name="xfile_software_hdr_summary">Tap to enable software HDR</string>
<string name="xfile_software_hdr_title" translatable="false">Google Pixel XL</string>
<string name="xfile_suggestion_summary_off">End screens are hidden</string>
<string name="xfile_suggestion_summary_on">End screens are shown</string>
<string name="xfile_suggestion_title">End screens</string>
<string name="xfile_support_summary">Support links</string>
<string name="xfile_support_title">Support</string>
<string name="xfile_video_settings_title">Video settings</string>
<string name="xfile_vp9_summary">Tap to start forcing the VP9 codec</string>
<string name="xfile_vp9_summary_off">VP9 codec not enabled</string>
<string name="xfile_vp9_summary_on">VP9 codec enabled for supported devices, disable if you encounter stuttering/slowness in videos</string>
<string name="xfile_vp9_title">VP9 codec</string>
<string name="xfile_xda_summary">Tap to open the XDA post</string>
<string name="xfile_xda_title">XDA thread</string>
<string name="xfile_new_actionbar_title">Wide search bar</string>
<string name="xfile_new_actionbar_summary_off">Search bar style is defined by the app</string>
<string name="xfile_new_actionbar_summary_on">Forcing wide search bar</string>
<string name="xfile_zoom_to_fit_vertical_title">Dynamic player</string>
<string name="xfile_zoom_to_fit_vertical_summary_off">Dynamic player is defined automatically</string>
<string name="xfile_zoom_to_fit_vertical_summary_on">Dynamic player is forced on square and vertical videos</string>
<string name="xfile_about_theme_summary">New official theme toggle is in the General settings. This theme toggle is \"Developer\" toggle.</string>
<string name="xfile_about_theme_title">Theme info</string>
<string name="xfile_accessibility_seek_buttons_summary_off">Accessibility controls aren\'t displayed in the player</string>
<string name="xfile_accessibility_seek_buttons_summary_on">Accessibility controls are displayed in the player</string>
<string name="xfile_accessibility_seek_buttons_title">Accessibility player</string>
<string name="xfile_auto_captions_summary_off">Captions aren\'t enabled automatically at 0% volume </string>
<string name="xfile_auto_captions_summary_on">Captions are enabled automatically at 0% volume</string>
<string name="xfile_auto_captions_title">Auto captions</string>
<string name="xfile_swipe_padding_top_summary">Amount of pixels excluded from swiping at the top of the display to prevent swipe controls when dragging down notifications</string>
<string name="xfile_swipe_padding_top_title">Swipe padding</string>
<string name="xfile_swipe_threshold_summary">Amount of pixels you have to swipe until detecting starts to prevent unintended swiping</string>
<string name="xfile_swipe_threshold_title">Swipe threshold</string>
<string name="xfile_xfenster_brightness_summary_off">Swipe controls for brightness are disabled</string>
<string name="xfile_xfenster_brightness_summary_on">Swipe controls for brightness are enabled</string>
<string name="xfile_xfenster_brightness_title">Swipe controls for Brightness</string>
<string name="xfile_xfenster_screen_summary">Swipe controls for Brightness and Volume</string>
<string name="xfile_xfenster_title">Swipe controls</string>
<string name="xfile_xfenster_volume_summary_off">Swipe controls for volume are disabled</string>
<string name="xfile_xfenster_volume_summary_on">Swipe controls for volume are enabled</string>
<string name="xfile_xfenster_volume_title">Swipe controls for Volume</string>
<string name="xfile_website_summary">Tap to open our website</string>
<string name="xfile_website_title">Vanced website</string>
<string name="xfile_home_ads_summary_off">Home ads are hidden</string>
<string name="xfile_home_ads_summary_on">Home ads are shown</string>
<string name="xfile_home_ads_title">Home ads (Experimental)</string>
<string name="xfile_reel_summary_off">Stories are hidden</string>
<string name="xfile_reel_summary_on">Stories are shown</string>
<string name="xfile_reel_title">YouTube stories (Experimental)</string>
<string name="xfile_ad_settings_title">Ad settings</string>
<string name="xfile_credit_summary">Credits for people who have contributed</string>
<string name="xfile_credit_title">Credits</string>
<string name="souramoo_summary">Home ads removing enhancement and showed other kinds of debugging methods</string>
<string name="souramoo_title" translatable="false">souramoo</string>
<string name="bawm_summary">SponsorBlock implementation</string>
<string name="bawm_title" translatable="false">JakubWeg</string>
<string name="enable_sb">Enable Sponsor Block (Beta)</string>
<string name="enable_sb_sum">Switch this on for very cool sponsor segments skipping</string>
<string name="enable_segmadding">Enable new segment adding</string>
<string name="enable_segmadding_sum">Switch this on to enable experimental segment adding (has button visibility issues).</string>
<string name="diff_segments">What to do with different segments</string>
<string name="general">General</string>
<string name="general_skiptoast">Show a toast when skipping segment automatically</string>
<string name="general_skiptoast_sum">Click to see an example toast</string>
<string name="general_skipcount">Skip count tracking</string>
<string name="general_skipcount_sum">This lets SponsorBlock leaderboard system know how much time people have saved. The extension sends a message to the server each time you skip a segment.</string>
<string name="general_adjusting">Adjusting new segment step</string>
<string name="general_adjusting_sum">This is a number of milliseconds you can move when clicking buttons when adding new segment</string>
<string name="general_uuid">Your unique user id</string>
<string name="general_uuid_sum">This should be kept private. This is like a password and should not be shared with anyone. If someone has this, they can impersonate you</string>
<string name="general_cache">Cache segments locally</string>
<string name="general_cache_sum">Frequently watched videos (eg. music videos) may store segments on this device to make skipping segments faster</string>
<string name="general_cache_clear">Clear sponsor block segments cache</string>
<string name="segments_sponsor">Sponsor</string>
<string name="segments_sponsor_sum">Paid promotion, paid referrals and direct advertisements</string>
<string name="segments_intermission">Intermission/Intro Animation</string>
<string name="segments_intermission_sum">An interval without actual content. Could be a pause, static frame, repeating animation</string>
<string name="segments_endcards">Endcards/Credits</string>
<string name="segments_endcards_sum">Credits or when the YouTube endcards appear. Not spoken conclusions</string>
<string name="segments_subscribe">Interaction Reminder (Subscribe)</string>
<string name="segments_subscribe_sum">When there is a short reminder to like, subscribe or follow them in the middle of content</string>
<string name="segments_selfpromo">Unpaid/Self Promotion</string>
<string name="segments_selfpromo_sum">Similar to "sponsor" except for unpaid or self promotion. This includes sections about merchandise, donations, or information about who they collaborated with</string>
<string name="segments_nomusic">Music: Non-Music Section</string>
<string name="segments_nomusic_sum">Only for use in music videos. This includes introductions or outros in music videos</string>
<string name="skipped_segment">Skipped a sponsor segment</string>
<string name="skipped_sponsor">Skipped sponsor</string>
<string name="skipped_intermission">Skipped intro</string>
<string name="skipped_endcard">Skipped outro</string>
<string name="skipped_subscribe">Skipped annoying reminder</string>
<string name="skipped_selfpromo">Skipped self promotion</string>
<string name="skipped_nomusic">Skipped silence</string>
<string name="skipped_preview">Skipped preview</string>
<string name="skip_automatically">Just skip, automatically</string>
<string name="skip_showbutton">Show skip button</string>
<string name="skip_ignore">Don\'t do anything</string>
<string name="about">About</string>
<string name="about_api">This app uses API from Sponsor Block</string>
<string name="about_api_sum">Click to learn more at: sponsor.ajay.app</string>
<string name="about_madeby">Integration made by JakubWeg</string>
<string name="tap_skip">Tap to skip</string>
<string name="app_name" />
</resources>

24
build.gradle Normal file
View File

@ -0,0 +1,24 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

19
gradle.properties Normal file
View File

@ -0,0 +1,19 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Tue Aug 18 22:56:28 EEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip

172
gradlew vendored Executable file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ]; do
ls=$(ls -ld "$PRG")
link=$(expr "$ls" : '.*-> \(.*\)$')
if expr "$link" : '/.*' >/dev/null; then
PRG="$link"
else
PRG=$(dirname "$PRG")"/$link"
fi
done
SAVED="$(pwd)"
cd "$(dirname \"$PRG\")/" >/dev/null
APP_HOME="$(pwd -P)"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=$(basename "$0")
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn() {
echo "$*"
}
die() {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$(uname)" in
CYGWIN*)
cygwin=true
;;
Darwin*)
darwin=true
;;
MINGW*)
msys=true
;;
NONSTOP*)
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ]; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ]; then
MAX_FD_LIMIT=$(ulimit -H -n)
if [ $? -eq 0 ]; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ]; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
APP_HOME=$(cygpath --path --mixed "$APP_HOME")
CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
JAVACMD=$(cygpath --unix "$JAVACMD")
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
SEP=""
for dir in $ROOTDIRSRAW; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ]; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@"; do
CHECK=$(echo "$arg" | egrep -c "$OURCYGPATTERN" -)
CHECK2=$(echo "$arg" | egrep -c "^-") ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ]; then ### Added a condition
eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg")
else
eval $(echo args$i)="\"$arg\""
fi
i=$((i + 1))
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save() {
for i; do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored Normal file
View File

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

2
settings.gradle Normal file
View File

@ -0,0 +1,2 @@
include ':app'
rootProject.name = "sb"