SponsorBlock: Added segment categories

Added category preferences with customizable colors. Also did some related preference/strings refactoring and updated the API calls to SponsorBlock to no longer use legacy versions.
This commit is contained in:
polymorphicshade 2020-08-04 17:56:21 -06:00
parent ed831376ff
commit a029c0ef9e
14 changed files with 508 additions and 147 deletions

View file

@ -4,18 +4,21 @@ import android.app.Application;
import android.content.Context; import android.content.Context;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.grack.nanojson.JsonArray; import com.grack.nanojson.JsonArray;
import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser; import com.grack.nanojson.JsonParser;
import org.schabi.newpipe.util.SponsorTimeInfo; import org.schabi.newpipe.util.VideoSegment;
import org.schabi.newpipe.util.TimeFrame;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
public class SponsorBlockApiTask extends AsyncTask<String, Void, JsonObject> { public class SponsorBlockApiTask extends AsyncTask<String, Void, JsonArray> {
private static final Application APP = App.getApp(); private static final Application APP = App.getApp();
private String apiUrl; private String apiUrl;
private static final String TAG = SponsorBlockApiTask.class.getSimpleName(); private static final String TAG = SponsorBlockApiTask.class.getSimpleName();
@ -25,30 +28,67 @@ public class SponsorBlockApiTask extends AsyncTask<String, Void, JsonObject> {
this.apiUrl = apiUrl; this.apiUrl = apiUrl;
} }
public SponsorTimeInfo getYouTubeVideoSponsorTimes(final String videoId) public VideoSegment[] getYouTubeVideoSegments(final String videoId,
throws ExecutionException, InterruptedException { final boolean includeSponsorCategory,
final boolean includeIntroCategory,
final boolean includeOutroCategory,
final boolean includeInteractionCategory,
final boolean includeSelfPromoCategory,
final boolean includeMusicCategory)
throws ExecutionException, InterruptedException, UnsupportedEncodingException {
JsonObject obj = execute("getVideoSponsorTimes?videoID=" + videoId).get(); ArrayList<String> categoryParamList = new ArrayList<>();
JsonArray arrayObj = obj.getArray("sponsorTimes");
SponsorTimeInfo result = new SponsorTimeInfo(); if (includeSponsorCategory) {
categoryParamList.add("sponsor");
for (int i = 0; i < arrayObj.size(); i++) { }
JsonArray subArrayObj = arrayObj.getArray(i); if (includeIntroCategory) {
categoryParamList.add("intro");
double startTime = subArrayObj.getDouble(0) * 1000; }
double endTime = subArrayObj.getDouble(1) * 1000; if (includeOutroCategory) {
categoryParamList.add("outro");
TimeFrame timeFrame = new TimeFrame(startTime, endTime); }
if (includeInteractionCategory) {
result.timeFrames.add(timeFrame); categoryParamList.add("interaction");
}
if (includeSelfPromoCategory) {
categoryParamList.add("selfpromo");
}
if (includeMusicCategory) {
categoryParamList.add("music_offtopic");
} }
return result; if (categoryParamList.size() == 0) {
return null;
}
String categoryParams = "[\"" + TextUtils.join("\",\"", categoryParamList) + "\"]";
categoryParams = URLEncoder.encode(categoryParams, "utf-8");
String params = "skipSegments?videoID=" + videoId + "&categories=" + categoryParams;
JsonArray arrayObj = execute(params).get();
ArrayList<VideoSegment> result = new ArrayList<>();
for (int i = 0; i < arrayObj.size(); i++) {
JsonObject obj = (JsonObject) arrayObj.get(i);
JsonArray segments = (JsonArray) obj.get("segment");
double startTime = segments.getDouble(0) * 1000;
double endTime = segments.getDouble(1) * 1000;
String category = obj.getString("category");
VideoSegment segment = new VideoSegment(startTime, endTime, category);
result.add(segment);
}
return result.toArray(new VideoSegment[0]);
} }
@Override @Override
protected JsonObject doInBackground(final String... strings) { protected JsonArray doInBackground(final String... strings) {
if (isCancelled() || !isConnected()) { if (isCancelled() || !isConnected()) {
return null; return null;
} }
@ -60,7 +100,7 @@ public class SponsorBlockApiTask extends AsyncTask<String, Void, JsonObject> {
.get(apiUrl + strings[0]) .get(apiUrl + strings[0])
.responseBody(); .responseBody();
return JsonParser.object().from(responseBody); return JsonParser.array().from(responseBody);
} catch (Exception ex) { } catch (Exception ex) {
if (DEBUG) { if (DEBUG) {

View file

@ -77,7 +77,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.resolver.MediaSourceTag; import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.SerializedCache; import org.schabi.newpipe.util.SerializedCache;
import org.schabi.newpipe.util.SponsorTimeInfo; import org.schabi.newpipe.util.VideoSegment;
import java.io.IOException; import java.io.IOException;
@ -114,9 +114,9 @@ public abstract class BasePlayer implements
public static final int STATE_COMPLETED = 128; public static final int STATE_COMPLETED = 128;
@NonNull @NonNull
private final SharedPreferences mPrefs; protected final SharedPreferences mPrefs;
private SponsorTimeInfo sponsorTimeInfo; private VideoSegment[] videoSegments;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Intent // Intent
@ -716,9 +716,8 @@ public abstract class BasePlayer implements
simpleExoPlayer.getBufferedPercentage() simpleExoPlayer.getBufferedPercentage()
); );
if (mPrefs.getBoolean(context.getString(R.string.sponsorblock_enable), false) if (mPrefs.getBoolean(context.getString(R.string.sponsorblock_enable_key), false)) {
&& sponsorTimeInfo != null) { int skipTo = getSponsorEndTimeFromProgress(currentProgress);
int skipTo = sponsorTimeInfo.getSponsorEndTimeFromProgress(currentProgress);
if (skipTo == 0) { if (skipTo == 0) {
return; return;
@ -726,8 +725,9 @@ public abstract class BasePlayer implements
seekTo(skipTo); seekTo(skipTo);
if (mPrefs.getBoolean(context.getString(R.string.sponsorblock_notifications), false)) { if (mPrefs.getBoolean(
String toastText = context.getString(R.string.sponsorblock_skipped_sponsor); context.getString(R.string.sponsorblock_notifications_key), false)) {
String toastText = context.getString(R.string.sponsorblock_skipped_segment);
Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show(); Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show();
} }
@ -738,6 +738,26 @@ public abstract class BasePlayer implements
} }
} }
private int getSponsorEndTimeFromProgress(final int progress) {
if (videoSegments == null) {
return 0;
}
for (VideoSegment segment : videoSegments) {
if (progress < segment.startTime) {
continue;
}
if (progress > segment.endTime) {
continue;
}
return (int) Math.ceil((segment.endTime));
}
return 0;
}
private Disposable getProgressReactor() { private Disposable getProgressReactor() {
return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS, mainThread()) return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS, mainThread())
.observeOn(mainThread()) .observeOn(mainThread())
@ -1101,14 +1121,56 @@ public abstract class BasePlayer implements
if (info.getUrl().startsWith("https://www.youtube.com")) { if (info.getUrl().startsWith("https://www.youtube.com")) {
String apiUrl = mPrefs String apiUrl = mPrefs
.getString(context.getString(R.string.sponsorblock_api_url), null); .getString(context.getString(R.string.sponsorblock_api_url_key), null);
boolean isSponsorBlockEnabled = mPrefs boolean isSponsorBlockEnabled = mPrefs
.getBoolean(context.getString(R.string.sponsorblock_enable), false); .getBoolean(context.getString(R.string.sponsorblock_enable_key), false);
if (apiUrl != null && !apiUrl.isEmpty() && isSponsorBlockEnabled) { if (apiUrl != null && !apiUrl.isEmpty() && isSponsorBlockEnabled) {
try { try {
sponsorTimeInfo = new SponsorBlockApiTask(apiUrl) boolean includeSponsorCategory =
.getYouTubeVideoSponsorTimes(info.getId()); mPrefs.getBoolean(
context.getString(
R.string.sponsorblock_category_sponsor_key),
false);
boolean includeIntroCategory =
mPrefs.getBoolean(
context.getString(
R.string.sponsorblock_category_intro_key),
false);
boolean includeOutroCategory =
mPrefs.getBoolean(
context.getString(
R.string.sponsorblock_category_outro_key),
false);
boolean includeInteractionCategory =
mPrefs.getBoolean(
context.getString(
R.string.sponsorblock_category_interaction_key),
false);
boolean includeSelfPromoCategory =
mPrefs.getBoolean(
context.getString(
R.string.sponsorblock_category_self_promo_key),
false);
boolean includeMusicCategory =
mPrefs.getBoolean(
context.getString(
R.string.sponsorblock_category_music_key),
false);
videoSegments = new SponsorBlockApiTask(apiUrl)
.getYouTubeVideoSegments(info.getId(),
includeSponsorCategory,
includeIntroCategory,
includeOutroCategory,
includeInteractionCategory,
includeSelfPromoCategory,
includeMusicCategory);
} catch (Exception e) { } catch (Exception e) {
Log.e("SPONSOR_BLOCK", "Error getting YouTube video sponsor times.", e); Log.e("SPONSOR_BLOCK", "Error getting YouTube video sponsor times.", e);
} }
@ -1653,11 +1715,7 @@ public abstract class BasePlayer implements
&& prefs.getBoolean(context.getString(R.string.enable_playback_resume_key), true); && prefs.getBoolean(context.getString(R.string.enable_playback_resume_key), true);
} }
public SponsorTimeInfo getSponsorTimeInfo() { public VideoSegment[] getVideoSegments() {
return sponsorTimeInfo; return videoSegments;
}
public void setSponsorTimeInfo(final SponsorTimeInfo sponsorTimeInfo) {
this.sponsorTimeInfo = sponsorTimeInfo;
} }
} }

View file

@ -69,9 +69,8 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.resolver.MediaSourceTag; import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.VideoSegment;
import org.schabi.newpipe.views.ExpandableSurfaceView; import org.schabi.newpipe.views.ExpandableSurfaceView;
import org.schabi.newpipe.util.SponsorTimeInfo;
import org.schabi.newpipe.util.TimeFrame;
import org.schabi.newpipe.views.MarkableSeekBar; import org.schabi.newpipe.views.MarkableSeekBar;
import org.schabi.newpipe.views.SeekBarMarker; import org.schabi.newpipe.views.SeekBarMarker;
@ -625,9 +624,9 @@ public abstract class VideoPlayer extends BasePlayer
} }
private void markSponsorTimes() { private void markSponsorTimes() {
SponsorTimeInfo sponsorTimeInfo = getSponsorTimeInfo(); VideoSegment[] segments = getVideoSegments();
if (sponsorTimeInfo == null) { if (segments == null || segments.length == 0) {
return; return;
} }
@ -637,10 +636,17 @@ public abstract class VideoPlayer extends BasePlayer
MarkableSeekBar markableSeekBar = (MarkableSeekBar) playbackSeekBar; MarkableSeekBar markableSeekBar = (MarkableSeekBar) playbackSeekBar;
for (TimeFrame timeFrame : sponsorTimeInfo.timeFrames) { for (VideoSegment segment : segments) {
Integer color = parseSegmentCategory(segment.category);
// if null, then this category should not be marked
if (color == null) {
continue;
}
SeekBarMarker seekBarMarker = SeekBarMarker seekBarMarker =
new SeekBarMarker(timeFrame.startTime, timeFrame.endTime, new SeekBarMarker(segment.startTime, segment.endTime,
(int) simpleExoPlayer.getDuration(), Color.GREEN); (int) simpleExoPlayer.getDuration(), color);
markableSeekBar.seekBarMarkers.add(seekBarMarker); markableSeekBar.seekBarMarkers.add(seekBarMarker);
markableSeekBar.invalidate(); markableSeekBar.invalidate();
@ -649,6 +655,75 @@ public abstract class VideoPlayer extends BasePlayer
} }
} }
private Integer parseSegmentCategory(final String category) {
String key;
String colorStr;
switch (category) {
case "sponsor":
key = context.getString(R.string.sponsorblock_category_sponsor_key);
if (mPrefs.getBoolean(key, false)) {
key = context.getString(R.string.sponsorblock_category_sponsor_color_key);
colorStr = mPrefs.getString(key, null);
return colorStr == null
? context.getResources().getColor(R.color.sponsor_segment)
: Color.parseColor(colorStr);
}
break;
case "intro":
key = context.getString(R.string.sponsorblock_category_intro_key);
if (mPrefs.getBoolean(key, false)) {
key = context.getString(R.string.sponsorblock_category_intro_color_key);
colorStr = mPrefs.getString(key, null);
return colorStr == null
? context.getResources().getColor(R.color.intro_segment)
: Color.parseColor(colorStr);
}
break;
case "outro":
key = context.getString(R.string.sponsorblock_category_outro_key);
if (mPrefs.getBoolean(key, false)) {
key = context.getString(R.string.sponsorblock_category_outro_color_key);
colorStr = mPrefs.getString(key, null);
return colorStr == null
? context.getResources().getColor(R.color.outro_segment)
: Color.parseColor(colorStr);
}
break;
case "interaction":
key = context.getString(R.string.sponsorblock_category_interaction_key);
if (mPrefs.getBoolean(key, false)) {
key = context.getString(R.string.sponsorblock_category_interaction_color_key);
colorStr = mPrefs.getString(key, null);
return colorStr == null
? context.getResources().getColor(R.color.interaction_segment)
: Color.parseColor(colorStr);
}
break;
case "selfpromo":
key = context.getString(R.string.sponsorblock_category_self_promo_key);
if (mPrefs.getBoolean(key, false)) {
key = context.getString(R.string.sponsorblock_category_self_promo_color_key);
colorStr = mPrefs.getString(key, null);
return colorStr == null
? context.getResources().getColor(R.color.self_promo_segment)
: Color.parseColor(colorStr);
}
break;
case "music_offtopic":
key = context.getString(R.string.sponsorblock_category_music_key);
if (mPrefs.getBoolean(key, false)) {
key = context.getString(R.string.sponsorblock_category_music_color_key);
colorStr = mPrefs.getString(key, null);
return colorStr == null
? context.getResources().getColor(R.color.music_offtopic_segment)
: Color.parseColor(colorStr);
}
break;
}
return null;
}
@Override @Override
public void destroy() { public void destroy() {
super.destroy(); super.destroy();

View file

@ -82,22 +82,26 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
@Override @Override
public boolean onPreferenceTreeClick(final Preference preference) { public boolean onPreferenceTreeClick(final Preference preference) {
if (preference.getKey().equals(thumbnailLoadToggleKey)) { String key = preference.getKey();
final ImageLoader imageLoader = ImageLoader.getInstance(); if (key != null) {
imageLoader.stop(); if (key.equals(thumbnailLoadToggleKey)) {
imageLoader.clearDiskCache(); final ImageLoader imageLoader = ImageLoader.getInstance();
imageLoader.clearMemoryCache(); imageLoader.stop();
imageLoader.resume(); imageLoader.clearDiskCache();
Toast.makeText(preference.getContext(), R.string.thumbnail_cache_wipe_complete_notice, imageLoader.clearMemoryCache();
Toast.LENGTH_SHORT).show(); imageLoader.resume();
} Toast.makeText(preference.getContext(),
R.string.thumbnail_cache_wipe_complete_notice,
Toast.LENGTH_SHORT).show();
}
if (preference.getKey().equals(youtubeRestrictedModeEnabledKey)) { if (key.equals(youtubeRestrictedModeEnabledKey)) {
Context context = getContext(); Context context = getContext();
if (context != null) { if (context != null) {
DownloaderImpl.getInstance().updateYoutubeRestrictedModeCookies(context); DownloaderImpl.getInstance().updateYoutubeRestrictedModeCookies(context);
} else { } else {
Log.w(TAG, "onPreferenceTreeClick: null context"); Log.w(TAG, "onPreferenceTreeClick: null context");
}
} }
} }
@ -142,7 +146,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
}); });
Preference sponsorBlockWebsitePreference = Preference sponsorBlockWebsitePreference =
findPreference(getString(R.string.sponsorblock_home_page)); findPreference(getString(R.string.sponsorblock_home_page_key));
sponsorBlockWebsitePreference.setOnPreferenceClickListener((Preference p) -> { sponsorBlockWebsitePreference.setOnPreferenceClickListener((Preference p) -> {
Intent i = new Intent(Intent.ACTION_VIEW, Intent i = new Intent(Intent.ACTION_VIEW,
Uri.parse(getString(R.string.sponsorblock_homepage_url))); Uri.parse(getString(R.string.sponsorblock_homepage_url)));
@ -151,7 +155,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
}); });
Preference sponsorBlockPrivacyPreference = Preference sponsorBlockPrivacyPreference =
findPreference(getString(R.string.sponsorblock_privacy)); findPreference(getString(R.string.sponsorblock_privacy_key));
sponsorBlockPrivacyPreference.setOnPreferenceClickListener((Preference p) -> { sponsorBlockPrivacyPreference.setOnPreferenceClickListener((Preference p) -> {
Intent i = new Intent(Intent.ACTION_VIEW, Intent i = new Intent(Intent.ACTION_VIEW,
Uri.parse(getString(R.string.sponsorblock_privacy_policy_url))); Uri.parse(getString(R.string.sponsorblock_privacy_policy_url)));
@ -160,17 +164,10 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
}); });
Preference sponsorBlockApiUrlPreference = Preference sponsorBlockApiUrlPreference =
findPreference(getString(R.string.sponsorblock_api_url)); findPreference(getString(R.string.sponsorblock_api_url_key));
// workaround to force dependency updates due to using a custom preference
sponsorBlockApiUrlPreference sponsorBlockApiUrlPreference
.setOnPreferenceChangeListener((preference, newValue) -> { .setOnPreferenceChangeListener((preference, newValue) -> {
findPreference(getString(R.string.sponsorblock_enable)) updateDependencies(preference, newValue);
.onDependencyChanged(preference,
newValue == null || newValue.equals(""));
findPreference(getString(R.string.sponsorblock_notifications))
.onDependencyChanged(preference,
newValue == null || newValue.equals(""));
return true; return true;
}); });
} }
@ -179,21 +176,13 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) { public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
// workaround to force dependency updates due to using a custom preference
Preference sponsorBlockApiUrlPreference = Preference sponsorBlockApiUrlPreference =
findPreference(getString(R.string.sponsorblock_api_url)); findPreference(getString(R.string.sponsorblock_api_url_key));
String sponsorBlockApiUrlPreferenceValue = String sponsorBlockApiUrlPreferenceValue =
getPreferenceManager() getPreferenceManager()
.getSharedPreferences() .getSharedPreferences()
.getString(getString(R.string.sponsorblock_api_url), null); .getString(getString(R.string.sponsorblock_api_url_key), null);
findPreference(getString(R.string.sponsorblock_enable)) updateDependencies(sponsorBlockApiUrlPreference, sponsorBlockApiUrlPreferenceValue);
.onDependencyChanged(sponsorBlockApiUrlPreference,
sponsorBlockApiUrlPreferenceValue == null
|| sponsorBlockApiUrlPreferenceValue.equals(""));
findPreference(getString(R.string.sponsorblock_notifications))
.onDependencyChanged(sponsorBlockApiUrlPreference,
sponsorBlockApiUrlPreferenceValue == null
|| sponsorBlockApiUrlPreferenceValue.equals(""));
} }
@Override @Override
@ -394,6 +383,23 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
} }
} }
private void updateDependencies(final Preference preference, final Object newValue) {
// This is a workaround to force dependency updates for custom preferences.
// sponsorblock_api_url_key
if (preference.getKey().equals(getString(R.string.sponsorblock_api_url_key))) {
findPreference(getString(R.string.sponsorblock_enable_key))
.onDependencyChanged(preference,
newValue == null || newValue.equals(""));
findPreference(getString(R.string.sponsorblock_notifications_key))
.onDependencyChanged(preference,
newValue == null || newValue.equals(""));
findPreference(getString(R.string.sponsorblock_categories_key))
.onDependencyChanged(preference,
newValue == null || newValue.equals(""));
}
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Error // Error
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/

View file

@ -45,6 +45,7 @@ public final class NewPipeSettings {
PreferenceManager.setDefaultValues(context, R.xml.main_settings, true); PreferenceManager.setDefaultValues(context, R.xml.main_settings, true);
PreferenceManager.setDefaultValues(context, R.xml.video_audio_settings, true); PreferenceManager.setDefaultValues(context, R.xml.video_audio_settings, true);
PreferenceManager.setDefaultValues(context, R.xml.debug_settings, true); PreferenceManager.setDefaultValues(context, R.xml.debug_settings, true);
PreferenceManager.setDefaultValues(context, R.xml.sponsor_block_category_settings, true);
getVideoDownloadFolder(context); getVideoDownloadFolder(context);
getAudioDownloadFolder(context); getAudioDownloadFolder(context);

View file

@ -0,0 +1,12 @@
package org.schabi.newpipe.settings;
import android.os.Bundle;
import org.schabi.newpipe.R;
public class SponsorBlockCategoriesSettingsFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.sponsor_block_category_settings);
}
}

View file

@ -1,27 +0,0 @@
package org.schabi.newpipe.util;
import java.util.ArrayList;
public class SponsorTimeInfo {
public ArrayList<TimeFrame> timeFrames = new ArrayList<>();
public int getSponsorEndTimeFromProgress(final int progress) {
if (timeFrames == null) {
return 0;
}
for (TimeFrame t : timeFrames) {
if (progress < t.startTime) {
continue;
}
if (progress > t.endTime) {
continue;
}
return (int) Math.ceil((t.endTime));
}
return 0;
}
}

View file

@ -1,12 +0,0 @@
package org.schabi.newpipe.util;
public class TimeFrame {
public double startTime;
public double endTime;
public Object tag;
public TimeFrame(final double startTime, final double endTime) {
this.startTime = startTime;
this.endTime = endTime;
}
}

View file

@ -0,0 +1,13 @@
package org.schabi.newpipe.util;
public class VideoSegment {
public double startTime;
public double endTime;
public String category;
public VideoSegment(final double startTime, final double endTime, final String category) {
this.startTime = startTime;
this.endTime = endTime;
this.category = category;
}
}

View file

@ -83,4 +83,11 @@
<color name="black">#000</color> <color name="black">#000</color>
<color name="gray_transparent">#be757575</color> <color name="gray_transparent">#be757575</color>
<!-- default SponsorBlock segment colors -->
<color name="sponsor_segment">#00d400</color>
<color name="intro_segment">#00ffff</color>
<color name="outro_segment">#0202ed</color>
<color name="interaction_segment">#cc00ff</color>
<color name="self_promo_segment">#ffff00</color>
<color name="music_offtopic_segment">#ff9900</color>
</resources> </resources>

View file

@ -239,11 +239,24 @@
<string name="downloads_storage_ask" translatable="false">downloads_storage_ask</string> <string name="downloads_storage_ask" translatable="false">downloads_storage_ask</string>
<string name="storage_use_saf" translatable="false">storage_use_saf</string> <string name="storage_use_saf" translatable="false">storage_use_saf</string>
<string name="sponsorblock_home_page" translatable="false">sponsorblock_home_page</string> <string name="sponsorblock_home_page_key" translatable="false">sponsorblock_home_page</string>
<string name="sponsorblock_enable" translatable="false">sponsorblock_enable</string> <string name="sponsorblock_enable_key" translatable="false">sponsorblock_enable</string>
<string name="sponsorblock_api_url" translatable="false">sponsorblock_api_url</string> <string name="sponsorblock_api_url_key" translatable="false">sponsorblock_api_url</string>
<string name="sponsorblock_notifications" translatable="false">sponsorblock_notifications</string> <string name="sponsorblock_notifications_key" translatable="false">sponsorblock_notifications</string>
<string name="sponsorblock_privacy" translatable="false">sponsorblock_privacy</string> <string name="sponsorblock_privacy_key" translatable="false">sponsorblock_privacy</string>
<string name="sponsorblock_categories_key" translatable="false">sponsorblock_categories</string>
<string name="sponsorblock_category_sponsor_key" translatable="false">sponsorblock_category_sponsor</string>
<string name="sponsorblock_category_sponsor_color_key" translatable="false">sponsorblock_category_sponsor_color</string>
<string name="sponsorblock_category_intro_key" translatable="false">sponsorblock_category_intro</string>
<string name="sponsorblock_category_intro_color_key" translatable="false">sponsorblock_category_intro_color</string>
<string name="sponsorblock_category_outro_key" translatable="false">sponsorblock_category_outro</string>
<string name="sponsorblock_category_outro_color_key" translatable="false">sponsorblock_category_outro_color</string>
<string name="sponsorblock_category_interaction_key" translatable="false">sponsorblock_category_interaction</string>
<string name="sponsorblock_category_interaction_color_key" translatable="false">sponsorblock_category_interaction_color</string>
<string name="sponsorblock_category_self_promo_key" translatable="false">sponsorblock_category_self_promo</string>
<string name="sponsorblock_category_self_promo_color_key" translatable="false">sponsorblock_category_self_promo_color</string>
<string name="sponsorblock_category_music_key" translatable="false">sponsorblock_category_music</string>
<string name="sponsorblock_category_music_color_key" translatable="false">sponsorblock_category_music_color</string>
<!-- FileName Downloads --> <!-- FileName Downloads -->
<string name="settings_file_charset_key" translatable="false">file_rename_charset</string> <string name="settings_file_charset_key" translatable="false">file_rename_charset</string>

View file

@ -132,7 +132,23 @@
<string name="settings_category_other_title">Other</string> <string name="settings_category_other_title">Other</string>
<string name="settings_category_debug_title">Debug</string> <string name="settings_category_debug_title">Debug</string>
<string name="settings_category_updates_title">Updates</string> <string name="settings_category_updates_title">Updates</string>
<string name="settings_category_sponsorblock">SponsorBlock (beta, third party service)</string> <string name="settings_category_sponsorblock_title">SponsorBlock (beta, third party service)</string>
<string name="settings_category_sponsorblock_categories_title">SponsorBlock Categories</string>
<string name="settings_category_sponsorblock_categories_summary">Customize which video segments to skip, along with their color markings on the seek bar.</string>
<string name="settings_category_sponsorblock_category_enable">Enable</string>
<string name="settings_category_sponsorblock_category_color">Seek Bar Color</string>
<string name="settings_category_sponsorblock_category_sponsor_title">Sponsor</string>
<string name="settings_category_sponsorblock_category_sponsor_summary">Paid promotion, paid referrals and direct advertisements. Not for self-promotion or free shoutouts to causes/creators/websites/products they like.</string>
<string name="settings_category_sponsorblock_category_intro_title">Intermission/Intro Animation</string>
<string name="settings_category_sponsorblock_category_intro_summary">An interval without actual content. Could be a pause, static frame, repeating animation. This should not be used for transitions containing information or be used on music videos.</string>
<string name="settings_category_sponsorblock_category_outro_title">Endcards/Credits</string>
<string name="settings_category_sponsorblock_category_outro_summary">Credits or when the YouTube endcards appear. Not for spoken conclusions. This should not include useful content. This should not be used on music videos.</string>
<string name="settings_category_sponsorblock_category_interaction_title">Interaction Reminds (Subscribe)</string>
<string name="settings_category_sponsorblock_category_interaction_summary">When there is a short reminder to like, subscribe or follow them in the middle of content. If it is long or about something specific, it should be under self promotion instead.</string>
<string name="settings_category_sponsorblock_category_self_promo_title">Unpaid/Self Promotion</string>
<string name="settings_category_sponsorblock_category_self_promo_summary">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="settings_category_sponsorblock_category_music_title">Music: Non-Music Section</string>
<string name="settings_category_sponsorblock_category_music_summary">Only for use in music videos. This includes introductions or outros in music videos.</string>
<string name="background_player_playing_toast">Playing in background</string> <string name="background_player_playing_toast">Playing in background</string>
<string name="popup_playing_toast">Playing in popup mode</string> <string name="popup_playing_toast">Playing in popup mode</string>
<string name="background_player_append">Queued on background player</string> <string name="background_player_append">Queued on background player</string>
@ -617,7 +633,7 @@
<string name="remove_watched_popup_warning">Videos that have been watched before and after being added to the playlist will be removed.\nAre you sure? This cannot be undone!</string> <string name="remove_watched_popup_warning">Videos that have been watched before and after being added to the playlist will be removed.\nAre you sure? This cannot be undone!</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">Yes, and partially watched videos</string> <string name="remove_watched_popup_yes_and_partially_watched_videos">Yes, and partially watched videos</string>
<string name="new_seek_duration_toast">Due to ExoPlayer constraints the seek duration was set to %d seconds</string> <string name="new_seek_duration_toast">Due to ExoPlayer constraints the seek duration was set to %d seconds</string>
<string name="sponsorblock_skipped_sponsor">Sponsor skipped</string> <string name="sponsorblock_skipped_segment">Segment skipped</string>
<!-- Time duration plurals --> <!-- Time duration plurals -->
<plurals name="seconds"> <plurals name="seconds">
<item quantity="one">%d second</item> <item quantity="one">%d second</item>

View file

@ -120,41 +120,49 @@
<PreferenceCategory <PreferenceCategory
android:layout="@layout/settings_category_header_layout" android:layout="@layout/settings_category_header_layout"
android:title="@string/settings_category_sponsorblock"> android:title="@string/settings_category_sponsorblock_title">
<Preference <Preference
android:key="@string/sponsorblock_home_page" app:iconSpaceReserved="false"
android:key="@string/sponsorblock_home_page_key"
android:summary="@string/sponsorblock_home_page_summary" android:summary="@string/sponsorblock_home_page_summary"
android:title="@string/sponsorblock_home_page_title" android:title="@string/sponsorblock_home_page_title"/>
app:iconSpaceReserved="false" />
<Preference <Preference
android:key="@string/sponsorblock_privacy" app:iconSpaceReserved="false"
android:key="@string/sponsorblock_privacy_key"
android:summary="@string/sponsorblock_privacy_summary" android:summary="@string/sponsorblock_privacy_summary"
android:title="@string/sponsorblock_privacy_title" android:title="@string/sponsorblock_privacy_title"/>
app:iconSpaceReserved="false" />
<org.schabi.newpipe.settings.custom.SponsorBlockApiUrlPreference <org.schabi.newpipe.settings.custom.SponsorBlockApiUrlPreference
android:key="@string/sponsorblock_api_url" app:iconSpaceReserved="false"
android:key="@string/sponsorblock_api_url_key"
android:summary="@string/sponsorblock_api_url_summary" android:summary="@string/sponsorblock_api_url_summary"
android:title="@string/sponsorblock_api_url_title" android:title="@string/sponsorblock_api_url_title"/>
app:iconSpaceReserved="false" />
<SwitchPreference <SwitchPreference
android:dependency="@string/sponsorblock_api_url" app:iconSpaceReserved="false"
android:dependency="@string/sponsorblock_api_url_key"
android:defaultValue="false" android:defaultValue="false"
android:key="@string/sponsorblock_enable" android:key="@string/sponsorblock_enable_key"
android:summary="@string/sponsorblock_enable_summary" android:summary="@string/sponsorblock_enable_summary"
android:title="@string/sponsorblock_enable_title" android:title="@string/sponsorblock_enable_title"/>
app:iconSpaceReserved="false" />
<SwitchPreference <SwitchPreference
app:iconSpaceReserved="false"
android:defaultValue="true" android:defaultValue="true"
android:dependency="@string/sponsorblock_api_url" android:dependency="@string/sponsorblock_api_url_key"
android:key="@string/sponsorblock_notifications" android:key="@string/sponsorblock_notifications_key"
android:summary="@string/sponsorblock_notifications_summary" android:summary="@string/sponsorblock_notifications_summary"
android:title="@string/sponsorblock_notifications_title" android:title="@string/sponsorblock_notifications_title"/>
app:iconSpaceReserved="false" />
<PreferenceScreen
app:iconSpaceReserved="false"
android:dependency="@string/sponsorblock_api_url_key"
android:fragment="org.schabi.newpipe.settings.SponsorBlockCategoriesSettingsFragment"
android:key="@string/sponsorblock_categories_key"
android:title="@string/settings_category_sponsorblock_categories_title"
android:summary="@string/settings_category_sponsorblock_categories_summary"/>
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View file

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/settings_category_sponsorblock_categories_title">
<PreferenceCategory
android:layout="@layout/settings_category_header_layout"
android:title="@string/settings_category_sponsorblock_category_sponsor_title">
<Preference
app:iconSpaceReserved="false"
android:summary="@string/settings_category_sponsorblock_category_sponsor_summary"
android:selectable="false"/>
<SwitchPreference
app:iconSpaceReserved="false"
android:defaultValue="true"
android:key="@string/sponsorblock_category_sponsor_key"
android:title="@string/settings_category_sponsorblock_category_enable"/>
<EditTextPreference
app:iconSpaceReserved="false"
android:dependency="@string/sponsorblock_category_sponsor_key"
android:defaultValue="@color/sponsor_segment"
android:key="@string/sponsorblock_category_sponsor_color_key"
android:title="@string/settings_category_sponsorblock_category_color"/>
</PreferenceCategory>
<PreferenceCategory
android:layout="@layout/settings_category_header_layout"
android:title="@string/settings_category_sponsorblock_category_intro_title">
<Preference
app:iconSpaceReserved="false"
android:summary="@string/settings_category_sponsorblock_category_intro_summary"
android:selectable="false"/>
<SwitchPreference
app:iconSpaceReserved="false"
android:defaultValue="false"
android:key="@string/sponsorblock_category_intro_key"
android:title="@string/settings_category_sponsorblock_category_enable"/>
<EditTextPreference
app:iconSpaceReserved="false"
android:dependency="@string/sponsorblock_category_intro_key"
android:defaultValue="@color/intro_segment"
android:key="@string/sponsorblock_category_intro_color_key"
android:title="@string/settings_category_sponsorblock_category_color"/>
</PreferenceCategory>
<PreferenceCategory
android:layout="@layout/settings_category_header_layout"
android:title="@string/settings_category_sponsorblock_category_outro_title">
<Preference
app:iconSpaceReserved="false"
android:summary="@string/settings_category_sponsorblock_category_outro_summary"
android:selectable="false"/>
<SwitchPreference
app:iconSpaceReserved="false"
android:defaultValue="false"
android:key="@string/sponsorblock_category_outro_key"
android:title="@string/settings_category_sponsorblock_category_enable"/>
<EditTextPreference
app:iconSpaceReserved="false"
android:dependency="@string/sponsorblock_category_outro_key"
android:defaultValue="@color/outro_segment"
android:key="@string/sponsorblock_category_outro_color_key"
android:title="@string/settings_category_sponsorblock_category_color"/>
</PreferenceCategory>
<PreferenceCategory
android:layout="@layout/settings_category_header_layout"
android:title="@string/settings_category_sponsorblock_category_interaction_title">
<Preference
app:iconSpaceReserved="false"
android:summary="@string/settings_category_sponsorblock_category_interaction_summary"
android:selectable="false"/>
<SwitchPreference
app:iconSpaceReserved="false"
android:defaultValue="false"
android:key="@string/sponsorblock_category_interaction_key"
android:title="@string/settings_category_sponsorblock_category_enable"/>
<EditTextPreference
app:iconSpaceReserved="false"
android:dependency="@string/sponsorblock_category_interaction_key"
android:defaultValue="@color/interaction_segment"
android:key="@string/sponsorblock_category_interaction_color_key"
android:title="@string/settings_category_sponsorblock_category_color"/>
</PreferenceCategory>
<PreferenceCategory
android:layout="@layout/settings_category_header_layout"
android:title="@string/settings_category_sponsorblock_category_self_promo_title">
<Preference
app:iconSpaceReserved="false"
android:summary="@string/settings_category_sponsorblock_category_self_promo_summary"
android:selectable="false"/>
<SwitchPreference
app:iconSpaceReserved="false"
android:defaultValue="false"
android:key="@string/sponsorblock_category_self_promo_key"
android:title="@string/settings_category_sponsorblock_category_enable"/>
<EditTextPreference
app:iconSpaceReserved="false"
android:dependency="@string/sponsorblock_category_self_promo_key"
android:defaultValue="@color/self_promo_segment"
android:key="@string/sponsorblock_category_self_promo_color_key"
android:title="@string/settings_category_sponsorblock_category_color"/>
</PreferenceCategory>
<PreferenceCategory
android:layout="@layout/settings_category_header_layout"
android:title="@string/settings_category_sponsorblock_category_music_title">
<Preference
app:iconSpaceReserved="false"
android:summary="@string/settings_category_sponsorblock_category_music_summary"
android:selectable="false"/>
<SwitchPreference
app:iconSpaceReserved="false"
android:defaultValue="false"
android:key="@string/sponsorblock_category_music_key"
android:title="@string/settings_category_sponsorblock_category_enable"/>
<EditTextPreference
app:iconSpaceReserved="false"
android:dependency="@string/sponsorblock_category_music_key"
android:defaultValue="@color/music_offtopic_segment"
android:key="@string/sponsorblock_category_music_color_key"
android:title="@string/settings_category_sponsorblock_category_color"/>
</PreferenceCategory>
</PreferenceScreen>