mirror of
https://github.com/MaintainTeam/LastPipeBender.git
synced 2025-02-28 21:38:20 +03:00
implemented SponsorBlock
This commit is contained in:
parent
8adedec08f
commit
b088c109a5
55 changed files with 2952 additions and 77 deletions
|
@ -2,7 +2,7 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 7,
|
||||
"identityHash": "012fc8e7ad3333f1597347f34e76a513",
|
||||
"identityHash": "7dcdec7a500be9088f7a9a4767292b41",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "subscriptions",
|
||||
|
@ -58,10 +58,10 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"uid"
|
||||
],
|
||||
"autoGenerate": true
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
|
@ -107,10 +107,10 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
|
@ -209,10 +209,10 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"uid"
|
||||
],
|
||||
"autoGenerate": true
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
|
@ -252,11 +252,11 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"stream_id",
|
||||
"access_date"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
|
@ -301,10 +301,10 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"stream_id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
|
@ -351,10 +351,10 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"uid"
|
||||
],
|
||||
"autoGenerate": true
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
|
@ -393,11 +393,11 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"playlist_id",
|
||||
"join_index"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
|
@ -493,10 +493,10 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"uid"
|
||||
],
|
||||
"autoGenerate": true
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
|
@ -539,11 +539,11 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"stream_id",
|
||||
"subscription_id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
|
@ -611,10 +611,10 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"uid"
|
||||
],
|
||||
"autoGenerate": true
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
|
@ -647,11 +647,11 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"group_id",
|
||||
"subscription_id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
|
@ -707,10 +707,10 @@
|
|||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"subscription_id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
|
@ -726,12 +726,32 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "sponsorblock_whitelist",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uploader` TEXT NOT NULL, PRIMARY KEY(`uploader`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "uploader",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"uploader"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '012fc8e7ad3333f1597347f34e76a513')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7dcdec7a500be9088f7a9a4767292b41')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -817,7 +817,8 @@ public class RouterActivity extends AppCompatActivity {
|
|||
inFlight(true);
|
||||
final LoadingDialog loadingDialog = new LoadingDialog(R.string.loading_metadata_title);
|
||||
loadingDialog.show(getParentFragmentManager(), "loadingDialog");
|
||||
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
|
||||
disposables.add(ExtractorHelper.getStreamInfo(getContext(), currentServiceId,
|
||||
currentUrl, true)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.compose(this::pleaseWait)
|
||||
|
@ -837,7 +838,8 @@ public class RouterActivity extends AppCompatActivity {
|
|||
|
||||
private void openAddToPlaylistDialog(final int currentServiceId, final String currentUrl) {
|
||||
inFlight(true);
|
||||
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, false)
|
||||
disposables.add(ExtractorHelper.getStreamInfo(getContext(), currentServiceId,
|
||||
currentUrl, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.compose(this::pleaseWait)
|
||||
|
@ -970,7 +972,8 @@ public class RouterActivity extends AppCompatActivity {
|
|||
|
||||
switch (choice.linkType) {
|
||||
case STREAM:
|
||||
single = ExtractorHelper.getStreamInfo(choice.serviceId, choice.url, false);
|
||||
single = ExtractorHelper.getStreamInfo(
|
||||
this, choice.serviceId, choice.url, false);
|
||||
userAction = UserAction.REQUESTED_STREAM;
|
||||
break;
|
||||
case CHANNEL:
|
||||
|
|
|
@ -22,6 +22,8 @@ import org.schabi.newpipe.database.playlist.dao.PlaylistStreamDAO;
|
|||
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||
import org.schabi.newpipe.database.sponsorblock.dao.SponsorBlockWhitelistDAO;
|
||||
import org.schabi.newpipe.database.sponsorblock.dao.SponsorBlockWhitelistEntry;
|
||||
import org.schabi.newpipe.database.stream.dao.StreamDAO;
|
||||
import org.schabi.newpipe.database.stream.dao.StreamStateDAO;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
|
@ -36,7 +38,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
|||
StreamEntity.class, StreamHistoryEntity.class, StreamStateEntity.class,
|
||||
PlaylistEntity.class, PlaylistStreamEntity.class, PlaylistRemoteEntity.class,
|
||||
FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
|
||||
FeedLastUpdatedEntity.class
|
||||
FeedLastUpdatedEntity.class, SponsorBlockWhitelistEntry.class
|
||||
},
|
||||
version = DB_VER_7
|
||||
)
|
||||
|
@ -62,4 +64,6 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||
public abstract FeedGroupDAO feedGroupDAO();
|
||||
|
||||
public abstract SubscriptionDAO subscriptionDAO();
|
||||
|
||||
public abstract SponsorBlockWhitelistDAO sponsorBlockWhitelistDAO();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package org.schabi.newpipe.database.sponsorblock.dao;
|
||||
|
||||
import static org.schabi.newpipe.database.sponsorblock.dao.SponsorBlockWhitelistEntry.SPONSORBLOCK_WHITELIST_TABLE;
|
||||
import static org.schabi.newpipe.database.sponsorblock.dao.SponsorBlockWhitelistEntry.UPLOADER;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Query;
|
||||
|
||||
import org.schabi.newpipe.database.BasicDAO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
|
||||
@Dao
|
||||
public abstract class SponsorBlockWhitelistDAO implements BasicDAO<SponsorBlockWhitelistEntry> {
|
||||
@Override
|
||||
@Query("SELECT * FROM " + SPONSORBLOCK_WHITELIST_TABLE)
|
||||
public abstract Flowable<List<SponsorBlockWhitelistEntry>> getAll();
|
||||
|
||||
@Override
|
||||
@Query("DELETE FROM " + SPONSORBLOCK_WHITELIST_TABLE)
|
||||
public abstract int deleteAll();
|
||||
|
||||
@Override
|
||||
public Flowable<List<SponsorBlockWhitelistEntry>> listByService(final int serviceId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Query("DELETE FROM " + SPONSORBLOCK_WHITELIST_TABLE + " WHERE " + UPLOADER + " = :uploader")
|
||||
public abstract int deleteByUploader(String uploader);
|
||||
|
||||
@Query("SELECT 1 FROM " + SPONSORBLOCK_WHITELIST_TABLE + " WHERE " + UPLOADER + " = :uploader")
|
||||
public abstract boolean exists(String uploader);
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package org.schabi.newpipe.database.sponsorblock.dao;
|
||||
|
||||
import static org.schabi.newpipe.database.sponsorblock.dao.SponsorBlockWhitelistEntry.SPONSORBLOCK_WHITELIST_TABLE;
|
||||
import static org.schabi.newpipe.database.sponsorblock.dao.SponsorBlockWhitelistEntry.UPLOADER;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
|
||||
@Entity(tableName = SPONSORBLOCK_WHITELIST_TABLE,
|
||||
primaryKeys = {UPLOADER}
|
||||
)
|
||||
public class SponsorBlockWhitelistEntry {
|
||||
public static final String SPONSORBLOCK_WHITELIST_TABLE = "sponsorblock_whitelist";
|
||||
public static final String UPLOADER = "uploader";
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = UPLOADER)
|
||||
private String uploader;
|
||||
|
||||
public SponsorBlockWhitelistEntry(final @NonNull String uploader) {
|
||||
this.uploader = uploader;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
public void setUploader(final @NonNull String uploader) {
|
||||
this.uploader = uploader;
|
||||
}
|
||||
}
|
|
@ -76,6 +76,10 @@ import org.schabi.newpipe.extractor.InfoItem;
|
|||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockAction;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockCategory;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockExtractorHelper;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockSegment;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.Stream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
|
@ -86,11 +90,14 @@ import org.schabi.newpipe.fragments.BaseStateFragment;
|
|||
import org.schabi.newpipe.fragments.EmptyFragment;
|
||||
import org.schabi.newpipe.fragments.MainFragment;
|
||||
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
|
||||
import org.schabi.newpipe.fragments.list.sponsorblock.SponsorBlockFragment;
|
||||
import org.schabi.newpipe.fragments.list.sponsorblock.SponsorBlockFragmentListener;
|
||||
import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment;
|
||||
import org.schabi.newpipe.ktx.AnimationType;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
|
||||
import org.schabi.newpipe.local.sponsorblock.SponsorBlockDataManager;
|
||||
import org.schabi.newpipe.player.Player;
|
||||
import org.schabi.newpipe.player.PlayerService;
|
||||
import org.schabi.newpipe.player.PlayerType;
|
||||
|
@ -110,12 +117,13 @@ import org.schabi.newpipe.util.ListHelper;
|
|||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.PlayButtonHelper;
|
||||
import org.schabi.newpipe.util.SponsorBlockMode;
|
||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.util.external_communication.KoreUtils;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.util.PlayButtonHelper;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
@ -128,6 +136,7 @@ import java.util.function.Consumer;
|
|||
|
||||
import icepick.State;
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
@ -136,7 +145,8 @@ public final class VideoDetailFragment
|
|||
extends BaseStateFragment<StreamInfo>
|
||||
implements BackPressable,
|
||||
PlayerServiceExtendedEventListener,
|
||||
OnKeyDownListener {
|
||||
OnKeyDownListener,
|
||||
SponsorBlockFragmentListener {
|
||||
public static final String KEY_SWITCHING_PLAYERS = "switching_players";
|
||||
|
||||
private static final float MAX_OVERLAY_ALPHA = 0.9f;
|
||||
|
@ -156,6 +166,7 @@ public final class VideoDetailFragment
|
|||
private static final String COMMENTS_TAB_TAG = "COMMENTS";
|
||||
private static final String RELATED_TAB_TAG = "NEXT VIDEO";
|
||||
private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB";
|
||||
private static final String SPONSOR_BLOCK_TAB_TAG = "SPONSOR_BLOCK TAB";
|
||||
private static final String EMPTY_TAB_TAG = "EMPTY TAB";
|
||||
|
||||
private static final String PICASSO_VIDEO_DETAILS_TAG = "PICASSO_VIDEO_DETAILS_TAG";
|
||||
|
@ -164,6 +175,7 @@ public final class VideoDetailFragment
|
|||
private boolean showComments;
|
||||
private boolean showRelatedItems;
|
||||
private boolean showDescription;
|
||||
private boolean showSponsorBlock;
|
||||
private String selectedTabTag;
|
||||
@AttrRes
|
||||
@NonNull
|
||||
|
@ -175,18 +187,25 @@ public final class VideoDetailFragment
|
|||
private int lastAppBarVerticalOffset = Integer.MAX_VALUE; // prevents useless updates
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener =
|
||||
(sharedPreferences, key) -> {
|
||||
if (getString(R.string.show_comments_key).equals(key)) {
|
||||
showComments = sharedPreferences.getBoolean(key, true);
|
||||
tabSettingsChanged = true;
|
||||
} else if (getString(R.string.show_next_video_key).equals(key)) {
|
||||
showRelatedItems = sharedPreferences.getBoolean(key, true);
|
||||
tabSettingsChanged = true;
|
||||
} else if (getString(R.string.show_description_key).equals(key)) {
|
||||
showDescription = sharedPreferences.getBoolean(key, true);
|
||||
tabSettingsChanged = true;
|
||||
}
|
||||
};
|
||||
this::onSharedPreferencesChanged;
|
||||
private Disposable workerIsWhitelisted;
|
||||
|
||||
private void onSharedPreferencesChanged(final SharedPreferences sharedPreferences,
|
||||
final String key) {
|
||||
if (getString(R.string.show_comments_key).equals(key)) {
|
||||
showComments = sharedPreferences.getBoolean(key, true);
|
||||
tabSettingsChanged = true;
|
||||
} else if (getString(R.string.show_next_video_key).equals(key)) {
|
||||
showRelatedItems = sharedPreferences.getBoolean(key, true);
|
||||
tabSettingsChanged = true;
|
||||
} else if (getString(R.string.show_description_key).equals(key)) {
|
||||
showDescription = sharedPreferences.getBoolean(key, true);
|
||||
tabSettingsChanged = true;
|
||||
} else if (getString(R.string.sponsor_block_enable_key).equals(key)) {
|
||||
showSponsorBlock = sharedPreferences.getBoolean(key, false);
|
||||
tabSettingsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@State
|
||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||
|
@ -212,6 +231,8 @@ public final class VideoDetailFragment
|
|||
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||
@Nullable
|
||||
private Disposable positionSubscriber = null;
|
||||
private Disposable submitSegmentSubscriber;
|
||||
|
||||
|
||||
private BottomSheetBehavior<FrameLayout> bottomSheetBehavior;
|
||||
private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback;
|
||||
|
@ -230,6 +251,7 @@ public final class VideoDetailFragment
|
|||
private PlayerService playerService;
|
||||
private Player player;
|
||||
private final PlayerHolder playerHolder = PlayerHolder.getInstance();
|
||||
private SponsorBlockDataManager sponsorBlockDataManager;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Service management
|
||||
|
@ -309,6 +331,7 @@ public final class VideoDetailFragment
|
|||
showComments = prefs.getBoolean(getString(R.string.show_comments_key), true);
|
||||
showRelatedItems = prefs.getBoolean(getString(R.string.show_next_video_key), true);
|
||||
showDescription = prefs.getBoolean(getString(R.string.show_description_key), true);
|
||||
showSponsorBlock = prefs.getBoolean(getString(R.string.sponsor_block_enable_key), false);
|
||||
selectedTabTag = prefs.getString(
|
||||
getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG);
|
||||
prefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
|
||||
|
@ -326,6 +349,8 @@ public final class VideoDetailFragment
|
|||
activity.getContentResolver().registerContentObserver(
|
||||
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false,
|
||||
settingsContentObserver);
|
||||
|
||||
sponsorBlockDataManager = new SponsorBlockDataManager(requireContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -426,6 +451,17 @@ public final class VideoDetailFragment
|
|||
binding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
if (submitSegmentSubscriber != null) {
|
||||
submitSegmentSubscriber.dispose();
|
||||
}
|
||||
if (workerIsWhitelisted != null) {
|
||||
workerIsWhitelisted.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
@ -552,10 +588,10 @@ public final class VideoDetailFragment
|
|||
}));
|
||||
|
||||
binding.detailControlsBackground.setOnLongClickListener(makeOnLongClickListener(info ->
|
||||
openBackgroundPlayer(true)
|
||||
openBackgroundPlayer(true)
|
||||
));
|
||||
binding.detailControlsPopup.setOnLongClickListener(makeOnLongClickListener(info ->
|
||||
openPopupPlayer(true)
|
||||
openPopupPlayer(true)
|
||||
));
|
||||
binding.detailControlsDownload.setOnLongClickListener(makeOnLongClickListener(info ->
|
||||
NavigationHelper.openDownloads(activity)));
|
||||
|
@ -838,7 +874,7 @@ public final class VideoDetailFragment
|
|||
|
||||
private void runWorker(final boolean forceLoad, final boolean addToBackStack) {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
currentWorker = ExtractorHelper.getStreamInfo(serviceId, url, forceLoad)
|
||||
currentWorker = ExtractorHelper.getStreamInfo(getContext(), serviceId, url, forceLoad)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(result -> {
|
||||
|
@ -901,6 +937,13 @@ public final class VideoDetailFragment
|
|||
tabContentDescriptions.add(R.string.description_tab_description);
|
||||
}
|
||||
|
||||
if (showSponsorBlock) {
|
||||
// temp empty fragment. will be updated in handleResult
|
||||
pageAdapter.addFragment(EmptyFragment.newInstance(false), SPONSOR_BLOCK_TAB_TAG);
|
||||
tabIcons.add(R.drawable.ic_sponsor_block_enable);
|
||||
tabContentDescriptions.add(R.string.sponsor_block_tab_description);
|
||||
}
|
||||
|
||||
if (pageAdapter.getCount() == 0) {
|
||||
pageAdapter.addFragment(EmptyFragment.newInstance(true), EMPTY_TAB_TAG);
|
||||
}
|
||||
|
@ -949,6 +992,13 @@ public final class VideoDetailFragment
|
|||
pageAdapter.updateItem(DESCRIPTION_TAB_TAG, new DescriptionFragment(info));
|
||||
}
|
||||
|
||||
if (showSponsorBlock) {
|
||||
final SponsorBlockFragment sponsorBlockFragment = new SponsorBlockFragment(info);
|
||||
sponsorBlockFragment.setListener(this);
|
||||
|
||||
pageAdapter.updateItem(SPONSOR_BLOCK_TAB_TAG, sponsorBlockFragment);
|
||||
}
|
||||
|
||||
binding.viewPager.setVisibility(View.VISIBLE);
|
||||
// make sure the tab layout is visible
|
||||
updateTabLayoutVisibility();
|
||||
|
@ -1243,6 +1293,9 @@ public final class VideoDetailFragment
|
|||
playerUi.removeViewFromParent();
|
||||
binding.playerPlaceholder.addView(playerUi.getBinding().getRoot());
|
||||
playerUi.setupVideoSurfaceIfNeeded();
|
||||
if (currentInfo != null) {
|
||||
playerUi.onMarkSeekbarRequested(currentInfo);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1786,6 +1839,9 @@ public final class VideoDetailFragment
|
|||
return;
|
||||
}
|
||||
|
||||
getSponsorBlockFragment().ifPresent(
|
||||
fragment -> fragment.setCurrentProgress(currentProgress));
|
||||
|
||||
if (player.getPlayQueue().getItem().getUrl().equals(url)) {
|
||||
updatePlaybackProgress(currentProgress, duration);
|
||||
}
|
||||
|
@ -1793,6 +1849,25 @@ public final class VideoDetailFragment
|
|||
|
||||
@Override
|
||||
public void onMetadataUpdate(final StreamInfo info, final PlayQueue queue) {
|
||||
final Context context = requireContext();
|
||||
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
final boolean isSponsorBlockEnabled =
|
||||
prefs.getBoolean(getString(R.string.sponsor_block_enable_key), false);
|
||||
|
||||
if (player != null && isSponsorBlockEnabled) {
|
||||
workerIsWhitelisted = sponsorBlockDataManager.isWhiteListed(info.getUploaderName())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(isWhitelisted -> {
|
||||
final SponsorBlockMode mode = isWhitelisted
|
||||
? SponsorBlockMode.DISABLED
|
||||
: SponsorBlockMode.ENABLED;
|
||||
player.setSponsorBlockMode(mode);
|
||||
getSponsorBlockFragment().ifPresent(
|
||||
fragment -> fragment.setSponsorBlockMode(mode));
|
||||
});
|
||||
}
|
||||
final StackItem item = findQueueInStack(queue);
|
||||
if (item != null) {
|
||||
// When PlayQueue can have multiple streams (PlaylistPlayQueue or ChannelPlayQueue)
|
||||
|
@ -2451,4 +2526,147 @@ public final class VideoDetailFragment
|
|||
lastStableBottomSheetState = newState;
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<SponsorBlockFragment> getSponsorBlockFragment() {
|
||||
final int sponsorBlockTabPos = pageAdapter.getItemPositionByTitle(SPONSOR_BLOCK_TAB_TAG);
|
||||
|
||||
if (sponsorBlockTabPos < 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
final Fragment fragment = pageAdapter.getItem(sponsorBlockTabPos);
|
||||
|
||||
if (fragment instanceof SponsorBlockFragment sponsorBlockFragment) {
|
||||
return Optional.of(sponsorBlockFragment);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSkippingEnabledChanged(final boolean newValue) {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.setSponsorBlockMode(newValue
|
||||
? SponsorBlockMode.ENABLED
|
||||
: SponsorBlockMode.DISABLED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestNewPendingSegment(final int startTime, final int endTime) {
|
||||
if (currentInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentInfo.removeSponsorBlockSegment("TEMP");
|
||||
|
||||
final SponsorBlockSegment segment = new SponsorBlockSegment(
|
||||
"TEMP",
|
||||
startTime,
|
||||
endTime,
|
||||
SponsorBlockCategory.PENDING,
|
||||
SponsorBlockAction.SKIP);
|
||||
|
||||
currentInfo.addSponsorBlockSegment(segment);
|
||||
|
||||
player.UIs().get(MainPlayerUi.class).ifPresent(
|
||||
playerUi -> playerUi.onMarkSeekbarRequested(currentInfo));
|
||||
|
||||
getSponsorBlockFragment().ifPresent(SponsorBlockFragment::refreshSponsorBlockSegments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestClearPendingSegment() {
|
||||
if (currentInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentInfo.removeSponsorBlockSegment("TEMP");
|
||||
|
||||
player.UIs().get(MainPlayerUi.class).ifPresent(
|
||||
playerUi -> playerUi.onMarkSeekbarRequested(currentInfo));
|
||||
|
||||
getSponsorBlockFragment().ifPresent(SponsorBlockFragment::refreshSponsorBlockSegments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestSubmitPendingSegment(final SponsorBlockSegment newSegment) {
|
||||
if (currentInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Context context = requireContext();
|
||||
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
final String apiUrl = prefs.getString(context
|
||||
.getString(R.string.sponsor_block_api_url_key), null);
|
||||
if (apiUrl == null || apiUrl.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
submitSegmentSubscriber = Single.fromCallable(() ->
|
||||
SponsorBlockExtractorHelper.submitSponsorBlockSegment(
|
||||
currentInfo,
|
||||
newSegment,
|
||||
""))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(response -> {
|
||||
if (response.responseCode() != 200) {
|
||||
String message = response.responseMessage();
|
||||
if (message.equals("")) {
|
||||
message = "Error " + response.responseCode();
|
||||
}
|
||||
Toast.makeText(context,
|
||||
message,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
currentInfo.removeSponsorBlockSegment("TEMP");
|
||||
currentInfo.addSponsorBlockSegment(newSegment);
|
||||
|
||||
player.UIs().get(MainPlayerUi.class).ifPresent(
|
||||
playerUi -> playerUi.onMarkSeekbarRequested(currentInfo));
|
||||
|
||||
getSponsorBlockFragment().ifPresent(
|
||||
SponsorBlockFragment::clearPendingSegment);
|
||||
|
||||
new AlertDialog
|
||||
.Builder(context)
|
||||
.setMessage(R.string.sponsor_block_upload_success_message)
|
||||
.setPositiveButton(R.string.ok, (d, w) -> d.dismiss())
|
||||
.show();
|
||||
}, throwable -> {
|
||||
if (throwable instanceof NullPointerException) {
|
||||
return;
|
||||
}
|
||||
ErrorUtil.showSnackbar(context,
|
||||
new ErrorInfo(throwable, UserAction.USER_REPORT,
|
||||
"Submit SponsorBlock segment"));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSeekToRequested(final long positionMillis) {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.seekTo(positionMillis);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,333 @@
|
|||
package org.schabi.newpipe.fragments.list.sponsorblock;
|
||||
|
||||
import static org.schabi.newpipe.util.TimeUtils.millisecondsToString;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.schabi.newpipe.BaseFragment;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.FragmentSponsorBlockBinding;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockAction;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockCategory;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockSegment;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.local.sponsorblock.SponsorBlockDataManager;
|
||||
import org.schabi.newpipe.util.SponsorBlockHelper;
|
||||
import org.schabi.newpipe.util.SponsorBlockMode;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class SponsorBlockFragment
|
||||
extends BaseFragment
|
||||
implements CompoundButton.OnCheckedChangeListener,
|
||||
SponsorBlockSegmentListAdapterListener {
|
||||
private StreamInfo streamInfo;
|
||||
FragmentSponsorBlockBinding binding;
|
||||
private Integer markedStartTime = null;
|
||||
private Integer markedEndTime = null;
|
||||
private SponsorBlockSegmentListAdapter segmentListAdapter;
|
||||
private int currentProgress = -1;
|
||||
private @Nullable SponsorBlockFragmentListener sponsorBlockFragmentListener;
|
||||
private SponsorBlockDataManager sponsorBlockDataManager;
|
||||
private Disposable workerIsWhitelisted;
|
||||
private Disposable workerAddToWhitelisted;
|
||||
private Disposable workerRemoveFromWhitelisted;
|
||||
private SponsorBlockMode sponsorBlockMode = SponsorBlockMode.ENABLED;
|
||||
|
||||
|
||||
public SponsorBlockFragment() {
|
||||
}
|
||||
|
||||
public SponsorBlockFragment(@NonNull final StreamInfo streamInfo) {
|
||||
this.streamInfo = streamInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
sponsorBlockDataManager = new SponsorBlockDataManager(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull final Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
if (streamInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
segmentListAdapter = new SponsorBlockSegmentListAdapter(context, this);
|
||||
segmentListAdapter.setItems(streamInfo.getSponsorBlockSegments());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
|
||||
if (workerIsWhitelisted != null) {
|
||||
workerIsWhitelisted.dispose();
|
||||
}
|
||||
if (workerAddToWhitelisted != null) {
|
||||
workerAddToWhitelisted.dispose();
|
||||
}
|
||||
if (workerRemoveFromWhitelisted != null) {
|
||||
workerRemoveFromWhitelisted.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
if (sponsorBlockDataManager != null) {
|
||||
sponsorBlockDataManager = new SponsorBlockDataManager(getContext());
|
||||
}
|
||||
|
||||
binding = FragmentSponsorBlockBinding.inflate(inflater, container, false);
|
||||
|
||||
binding.sponsorBlockControlsMarkSegmentStart.setOnClickListener(v ->
|
||||
doMarkPendingSegment(true));
|
||||
binding.sponsorBlockControlsMarkSegmentEnd.setOnClickListener(v ->
|
||||
doMarkPendingSegment(false));
|
||||
binding.sponsorBlockControlsSegmentStart.setOnClickListener(v ->
|
||||
doPendingSegmentSeek(true));
|
||||
binding.sponsorBlockControlsSegmentEnd.setOnClickListener(v ->
|
||||
doPendingSegmentSeek(false));
|
||||
binding.sponsorBlockControlsClearSegment.setOnClickListener(v ->
|
||||
doClearPendingSegment());
|
||||
binding.sponsorBlockControlsSubmitSegment.setOnClickListener(v ->
|
||||
doSubmitPendingSegment());
|
||||
|
||||
binding.segmentList.setAdapter(segmentListAdapter);
|
||||
|
||||
workerIsWhitelisted = sponsorBlockDataManager.isWhiteListed(streamInfo.getUploaderName())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(isWhitelisted -> {
|
||||
binding.channelIsWhitelistedSwitch.setChecked(isWhitelisted);
|
||||
|
||||
binding.skippingIsEnabledSwitch.setChecked(
|
||||
!isWhitelisted && sponsorBlockMode == SponsorBlockMode.ENABLED);
|
||||
|
||||
binding.skippingIsEnabledSwitch.setOnCheckedChangeListener(this);
|
||||
binding.channelIsWhitelistedSwitch.setOnCheckedChangeListener(this);
|
||||
});
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
||||
if (buttonView.getId() == R.id.skipping_is_enabled_switch) {
|
||||
if (sponsorBlockFragmentListener != null) {
|
||||
sponsorBlockFragmentListener.onSkippingEnabledChanged(isChecked);
|
||||
}
|
||||
} else if (buttonView.getId() == R.id.channel_is_whitelisted_switch) {
|
||||
final Context context = requireContext();
|
||||
|
||||
final String toastText;
|
||||
|
||||
if (isChecked) {
|
||||
toastText = context.getString(
|
||||
R.string.sponsor_block_uploader_added_to_whitelist_toast);
|
||||
|
||||
workerAddToWhitelisted =
|
||||
sponsorBlockDataManager.addToWhitelist(streamInfo.getUploaderName())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(result -> {
|
||||
Toast.makeText(context, toastText, Toast.LENGTH_LONG).show();
|
||||
}, error -> {
|
||||
// TODO
|
||||
});
|
||||
} else {
|
||||
toastText = context.getString(
|
||||
R.string.sponsor_block_uploader_removed_from_whitelist_toast);
|
||||
|
||||
workerRemoveFromWhitelisted =
|
||||
sponsorBlockDataManager.removeFromWhitelist(streamInfo.getUploaderName())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
Toast.makeText(context, toastText, Toast.LENGTH_LONG).show();
|
||||
}, error -> {
|
||||
// TODO
|
||||
});
|
||||
}
|
||||
|
||||
binding.skippingIsEnabledSwitch.setChecked(false);
|
||||
binding.skippingIsEnabledSwitch.setEnabled(!isChecked);
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(final SponsorBlockFragmentListener listener) {
|
||||
sponsorBlockFragmentListener = listener;
|
||||
}
|
||||
|
||||
public void setSponsorBlockMode(@NonNull final SponsorBlockMode mode) {
|
||||
sponsorBlockMode = mode;
|
||||
}
|
||||
|
||||
public void setCurrentProgress(final int progress) {
|
||||
currentProgress = progress;
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
public void clearPendingSegment() {
|
||||
markedStartTime = null;
|
||||
markedEndTime = null;
|
||||
|
||||
binding.sponsorBlockControlsSegmentStart.setText("00:00:00");
|
||||
binding.sponsorBlockControlsSegmentEnd.setText("00:00:00");
|
||||
|
||||
if (sponsorBlockFragmentListener != null) {
|
||||
sponsorBlockFragmentListener.onRequestClearPendingSegment();
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshSponsorBlockSegments() {
|
||||
if (segmentListAdapter == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
segmentListAdapter.setItems(streamInfo.getSponsorBlockSegments());
|
||||
}
|
||||
|
||||
private void doMarkPendingSegment(final boolean isStart) {
|
||||
if (currentProgress < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isStart) {
|
||||
if (markedEndTime != null && currentProgress > markedEndTime) {
|
||||
Toast.makeText(getContext(),
|
||||
getString(R.string.sponsor_block_invalid_start_toast),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
markedStartTime = currentProgress;
|
||||
} else {
|
||||
if (markedStartTime != null && currentProgress < markedStartTime) {
|
||||
Toast.makeText(getContext(),
|
||||
getString(R.string.sponsor_block_invalid_end_toast),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
markedEndTime = currentProgress;
|
||||
}
|
||||
|
||||
if (markedStartTime != null) {
|
||||
binding.sponsorBlockControlsSegmentStart.setText(
|
||||
millisecondsToString(markedStartTime));
|
||||
}
|
||||
|
||||
if (markedEndTime != null) {
|
||||
binding.sponsorBlockControlsSegmentEnd.setText(
|
||||
millisecondsToString(markedEndTime));
|
||||
}
|
||||
|
||||
if (markedStartTime != null && markedEndTime != null) {
|
||||
if (sponsorBlockFragmentListener != null) {
|
||||
sponsorBlockFragmentListener.onRequestNewPendingSegment(
|
||||
markedStartTime, markedEndTime);
|
||||
}
|
||||
}
|
||||
|
||||
final String message = isStart
|
||||
? getString(R.string.sponsor_block_marked_start_toast)
|
||||
: getString(R.string.sponsor_block_marked_end_toast);
|
||||
Toast.makeText(getContext(),
|
||||
message,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void doClearPendingSegment() {
|
||||
new AlertDialog
|
||||
.Builder(requireContext())
|
||||
.setMessage(R.string.sponsor_block_clear_marked_segment_prompt)
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss())
|
||||
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||
clearPendingSegment();
|
||||
dialog.dismiss();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void doPendingSegmentSeek(final boolean isStart) {
|
||||
if (isStart && markedStartTime != null) {
|
||||
onSkipToTimestampRequested((long) markedStartTime);
|
||||
} else if (markedEndTime != null) {
|
||||
onSkipToTimestampRequested((long) markedEndTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void doSubmitPendingSegment() {
|
||||
final Context context = requireContext();
|
||||
|
||||
if (markedStartTime == null || markedEndTime == null) {
|
||||
Toast.makeText(context,
|
||||
getString(R.string.sponsor_block_missing_times_toast),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.sponsor_block_select_a_category);
|
||||
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
|
||||
builder.setItems(new String[]{
|
||||
SponsorBlockHelper.convertCategoryToFriendlyName(
|
||||
context, SponsorBlockCategory.SPONSOR),
|
||||
SponsorBlockHelper.convertCategoryToFriendlyName(
|
||||
context, SponsorBlockCategory.INTRO),
|
||||
SponsorBlockHelper.convertCategoryToFriendlyName(
|
||||
context, SponsorBlockCategory.OUTRO),
|
||||
SponsorBlockHelper.convertCategoryToFriendlyName(
|
||||
context, SponsorBlockCategory.INTERACTION),
|
||||
SponsorBlockHelper.convertCategoryToFriendlyName(
|
||||
context, SponsorBlockCategory.HIGHLIGHT),
|
||||
SponsorBlockHelper.convertCategoryToFriendlyName(
|
||||
context, SponsorBlockCategory.SELF_PROMO),
|
||||
SponsorBlockHelper.convertCategoryToFriendlyName(
|
||||
context, SponsorBlockCategory.NON_MUSIC),
|
||||
SponsorBlockHelper.convertCategoryToFriendlyName(
|
||||
context, SponsorBlockCategory.PREVIEW),
|
||||
SponsorBlockHelper.convertCategoryToFriendlyName(
|
||||
context, SponsorBlockCategory.FILLER)
|
||||
}, (dialog, which) -> {
|
||||
final SponsorBlockCategory category = SponsorBlockCategory.values()[which];
|
||||
final SponsorBlockAction action = category == SponsorBlockCategory.HIGHLIGHT
|
||||
? SponsorBlockAction.POI
|
||||
: SponsorBlockAction.SKIP;
|
||||
final SponsorBlockSegment newSegment =
|
||||
new SponsorBlockSegment(
|
||||
"", markedStartTime, markedEndTime, category, action);
|
||||
if (sponsorBlockFragmentListener != null) {
|
||||
sponsorBlockFragmentListener.onRequestSubmitPendingSegment(newSegment);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSkipToTimestampRequested(final long positionMillis) {
|
||||
if (sponsorBlockFragmentListener != null) {
|
||||
sponsorBlockFragmentListener.onSeekToRequested(positionMillis);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package org.schabi.newpipe.fragments.list.sponsorblock;
|
||||
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockSegment;
|
||||
|
||||
public interface SponsorBlockFragmentListener {
|
||||
void onSkippingEnabledChanged(boolean newValue);
|
||||
void onRequestNewPendingSegment(int startTime, int endTime);
|
||||
void onRequestClearPendingSegment();
|
||||
void onRequestSubmitPendingSegment(SponsorBlockSegment newSegment);
|
||||
void onSeekToRequested(long positionMillis);
|
||||
}
|
|
@ -0,0 +1,294 @@
|
|||
package org.schabi.newpipe.fragments.list.sponsorblock;
|
||||
|
||||
import static org.schabi.newpipe.util.TimeUtils.millisecondsToString;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockCategory;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockExtractorHelper;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockSegment;
|
||||
import org.schabi.newpipe.util.SponsorBlockHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class SponsorBlockSegmentListAdapter extends
|
||||
RecyclerView.Adapter<SponsorBlockSegmentListAdapter.SponsorBlockSegmentItemViewHolder> {
|
||||
private final Context context;
|
||||
private ArrayList<SponsorBlockSegment> sponsorBlockSegments = new ArrayList<>();
|
||||
private final SponsorBlockSegmentListAdapterListener listener;
|
||||
|
||||
public SponsorBlockSegmentListAdapter(final Context context,
|
||||
final SponsorBlockSegmentListAdapterListener listener) {
|
||||
this.context = context;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void setItems(final SponsorBlockSegment[] items) {
|
||||
if (items == null) {
|
||||
sponsorBlockSegments.clear();
|
||||
} else {
|
||||
sponsorBlockSegments = new ArrayList<>(Arrays.asList(items));
|
||||
}
|
||||
|
||||
// find the first "highlight" segment (if it exists) and move it to the top
|
||||
if (sponsorBlockSegments.size() > 0) {
|
||||
final Optional<SponsorBlockSegment> highlightSegment =
|
||||
sponsorBlockSegments
|
||||
.stream()
|
||||
.filter(x -> x.category == SponsorBlockCategory.HIGHLIGHT)
|
||||
.findFirst();
|
||||
|
||||
if (highlightSegment.isPresent()) {
|
||||
sponsorBlockSegments.remove(highlightSegment.get());
|
||||
sponsorBlockSegments.add(0, highlightSegment.get());
|
||||
}
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SponsorBlockSegmentListAdapter.SponsorBlockSegmentItemViewHolder onCreateViewHolder(
|
||||
@NonNull final ViewGroup parent, final int viewType) {
|
||||
final View itemView = LayoutInflater
|
||||
.from(context)
|
||||
.inflate(R.layout.list_segments_item, parent, false);
|
||||
return new SponsorBlockSegmentItemViewHolder(itemView, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(
|
||||
@NonNull final SponsorBlockSegmentListAdapter.SponsorBlockSegmentItemViewHolder holder,
|
||||
final int position) {
|
||||
final SponsorBlockSegment sponsorBlockSegment = sponsorBlockSegments.get(position);
|
||||
holder.updateFrom(sponsorBlockSegment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return sponsorBlockSegments.size();
|
||||
}
|
||||
|
||||
public static class SponsorBlockSegmentItemViewHolder extends RecyclerView.ViewHolder {
|
||||
private final View itemSegmentColorView;
|
||||
private final ImageView itemSegmentSkipToHighlight;
|
||||
private final TextView itemSegmentNameTextView;
|
||||
private final TextView itemSegmentStartTimeTextView;
|
||||
private final TextView itemSegmentEndTimeTextView;
|
||||
private final ImageView itemSegmentVoteUpImageView;
|
||||
private final ImageView itemSegmentVoteDownImageView;
|
||||
private Disposable voteSubscriber;
|
||||
private String segmentUuid;
|
||||
private boolean isVoting;
|
||||
private boolean hasUpVoted;
|
||||
private boolean hasDownVoted;
|
||||
private boolean hasResetVote;
|
||||
private SponsorBlockSegment currentSponsorBlockSegment;
|
||||
|
||||
public SponsorBlockSegmentItemViewHolder(
|
||||
@NonNull final View itemView,
|
||||
final SponsorBlockSegmentListAdapterListener listener) {
|
||||
super(itemView);
|
||||
|
||||
itemSegmentColorView = itemView.findViewById(R.id.item_segment_color_view);
|
||||
itemSegmentSkipToHighlight = itemView.findViewById(R.id.item_segment_skip_to_highlight);
|
||||
itemSegmentSkipToHighlight.setOnClickListener(v -> {
|
||||
if (currentSponsorBlockSegment != null && listener != null) {
|
||||
listener.onSkipToTimestampRequested(
|
||||
(long) currentSponsorBlockSegment.startTime);
|
||||
}
|
||||
});
|
||||
itemSegmentNameTextView = itemView.findViewById(
|
||||
R.id.item_segment_category_name_textview);
|
||||
itemSegmentStartTimeTextView = itemView.findViewById(
|
||||
R.id.item_segment_start_time_textview);
|
||||
itemSegmentStartTimeTextView.setOnClickListener(v -> {
|
||||
if (currentSponsorBlockSegment != null && listener != null) {
|
||||
listener.onSkipToTimestampRequested(
|
||||
(long) currentSponsorBlockSegment.startTime);
|
||||
}
|
||||
});
|
||||
itemSegmentEndTimeTextView = itemView.findViewById(R.id.item_segment_end_time_textview);
|
||||
itemSegmentEndTimeTextView.setOnClickListener(v -> {
|
||||
if (currentSponsorBlockSegment != null && listener != null) {
|
||||
listener.onSkipToTimestampRequested((long) currentSponsorBlockSegment.endTime);
|
||||
}
|
||||
});
|
||||
|
||||
// voting:
|
||||
// 1 = up
|
||||
// 0 = down
|
||||
// 20 = reset
|
||||
itemSegmentVoteUpImageView =
|
||||
itemView.findViewById(R.id.item_segment_vote_up_imageview);
|
||||
itemSegmentVoteUpImageView.setOnClickListener(v -> vote(1));
|
||||
itemSegmentVoteUpImageView.setOnLongClickListener(v -> {
|
||||
vote(20);
|
||||
return true;
|
||||
});
|
||||
itemSegmentVoteDownImageView =
|
||||
itemView.findViewById(R.id.item_segment_vote_down_imageview);
|
||||
itemSegmentVoteDownImageView.setOnClickListener(v -> vote(0));
|
||||
itemSegmentVoteDownImageView.setOnLongClickListener(v -> {
|
||||
vote(20);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void updateFrom(final SponsorBlockSegment sponsorBlockSegment) {
|
||||
currentSponsorBlockSegment = sponsorBlockSegment;
|
||||
|
||||
final Context context = itemView.getContext();
|
||||
|
||||
// uuid
|
||||
segmentUuid = sponsorBlockSegment.uuid;
|
||||
|
||||
// category color
|
||||
final Integer segmentColor =
|
||||
SponsorBlockHelper.convertCategoryToColor(
|
||||
sponsorBlockSegment.category, context);
|
||||
if (segmentColor != null) {
|
||||
itemSegmentColorView.setBackgroundColor(segmentColor);
|
||||
}
|
||||
|
||||
// skip to highlight
|
||||
if (sponsorBlockSegment.category == SponsorBlockCategory.HIGHLIGHT) {
|
||||
itemSegmentColorView.setVisibility(View.GONE);
|
||||
itemSegmentSkipToHighlight.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
itemSegmentColorView.setVisibility(View.VISIBLE);
|
||||
itemSegmentSkipToHighlight.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// category name
|
||||
final String friendlyCategoryName =
|
||||
SponsorBlockHelper.convertCategoryToFriendlyName(
|
||||
context, sponsorBlockSegment.category);
|
||||
itemSegmentNameTextView.setText(friendlyCategoryName);
|
||||
|
||||
// from
|
||||
final String startText = millisecondsToString(sponsorBlockSegment.startTime);
|
||||
itemSegmentStartTimeTextView.setText(startText);
|
||||
|
||||
// to
|
||||
final String endText = millisecondsToString(sponsorBlockSegment.endTime);
|
||||
itemSegmentEndTimeTextView.setText(endText);
|
||||
|
||||
if (sponsorBlockSegment.category == SponsorBlockCategory.PENDING
|
||||
|| sponsorBlockSegment.uuid.equals("TEMP")
|
||||
|| sponsorBlockSegment.uuid.equals("")) {
|
||||
itemSegmentVoteUpImageView.setVisibility(View.INVISIBLE);
|
||||
itemSegmentVoteDownImageView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void vote(final int value) {
|
||||
if (segmentUuid == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVoting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (voteSubscriber != null) {
|
||||
voteSubscriber.dispose();
|
||||
}
|
||||
|
||||
// these 3 checks prevent the user from continuously spamming votes
|
||||
// (not entirely sure if we need this)
|
||||
|
||||
if (value == 0 && hasDownVoted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == 1 && hasUpVoted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == 20 && hasResetVote) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Context context = itemView.getContext();
|
||||
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
final String apiUrl = prefs.getString(context
|
||||
.getString(R.string.sponsor_block_api_url_key), null);
|
||||
if (apiUrl == null || apiUrl.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
voteSubscriber = Single.fromCallable(() -> {
|
||||
isVoting = true;
|
||||
return SponsorBlockExtractorHelper.submitSponsorBlockSegmentVote(
|
||||
segmentUuid, apiUrl, value);
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(response -> {
|
||||
isVoting = false;
|
||||
String toastMessage;
|
||||
if (response.responseCode() != 200) {
|
||||
toastMessage = response.responseMessage();
|
||||
if (toastMessage.equals("")) {
|
||||
toastMessage = "Error " + response.responseCode();
|
||||
}
|
||||
} else if (value == 0) {
|
||||
hasDownVoted = true;
|
||||
hasUpVoted = false;
|
||||
hasResetVote = false;
|
||||
toastMessage = context.getString(
|
||||
R.string.sponsor_block_segment_voted_down_toast);
|
||||
} else if (value == 1) {
|
||||
hasDownVoted = false;
|
||||
hasUpVoted = true;
|
||||
hasResetVote = false;
|
||||
toastMessage = context.getString(
|
||||
R.string.sponsor_block_segment_voted_up_toast);
|
||||
} else if (value == 20) {
|
||||
hasDownVoted = false;
|
||||
hasUpVoted = false;
|
||||
hasResetVote = true;
|
||||
toastMessage = context.getString(
|
||||
R.string.sponsor_block_segment_reset_vote_toast);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
Toast.makeText(context,
|
||||
toastMessage,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}, throwable -> {
|
||||
if (throwable instanceof NullPointerException) {
|
||||
return;
|
||||
}
|
||||
ErrorUtil.showSnackbar(context,
|
||||
new ErrorInfo(throwable, UserAction.SUBSCRIPTION_UPDATE,
|
||||
"Submit vote for SponsorBlock segment"));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.schabi.newpipe.fragments.list.sponsorblock;
|
||||
|
||||
public interface SponsorBlockSegmentListAdapterListener {
|
||||
void onSkipToTimestampRequested(long positionMillis);
|
||||
}
|
|
@ -59,6 +59,7 @@ import io.reactivex.rxjava3.core.Single;
|
|||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class HistoryRecordManager {
|
||||
private final Context context;
|
||||
private final AppDatabase database;
|
||||
private final StreamDAO streamTable;
|
||||
private final StreamHistoryDAO streamHistoryTable;
|
||||
|
@ -69,6 +70,7 @@ public class HistoryRecordManager {
|
|||
private final String streamHistoryKey;
|
||||
|
||||
public HistoryRecordManager(final Context context) {
|
||||
this.context = context;
|
||||
database = NewPipeDatabase.getInstance(context);
|
||||
streamTable = database.streamDAO();
|
||||
streamHistoryTable = database.streamHistoryDAO();
|
||||
|
@ -103,6 +105,7 @@ public class HistoryRecordManager {
|
|||
// Duration will not exist if the item was loaded with fast mode, so fetch it if empty
|
||||
if (info.getDuration() < 0) {
|
||||
final StreamInfo completeInfo = ExtractorHelper.getStreamInfo(
|
||||
context,
|
||||
info.getServiceId(),
|
||||
info.getUrl(),
|
||||
false
|
||||
|
@ -235,7 +238,7 @@ public class HistoryRecordManager {
|
|||
///////////////////////////////////////////////////////
|
||||
|
||||
public Maybe<StreamStateEntity> loadStreamState(final PlayQueueItem queueItem) {
|
||||
return queueItem.getStream()
|
||||
return queueItem.getStream(context)
|
||||
.map(info -> streamTable.upsert(new StreamEntity(info)))
|
||||
.flatMapPublisher(streamStateTable::getState)
|
||||
.firstElement()
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package org.schabi.newpipe.local.sponsorblock;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.database.AppDatabase;
|
||||
import org.schabi.newpipe.database.sponsorblock.dao.SponsorBlockWhitelistDAO;
|
||||
import org.schabi.newpipe.database.sponsorblock.dao.SponsorBlockWhitelistEntry;
|
||||
|
||||
import io.reactivex.rxjava3.core.Completable;
|
||||
import io.reactivex.rxjava3.core.Maybe;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class SponsorBlockDataManager {
|
||||
private final SponsorBlockWhitelistDAO sponsorBlockWhitelistTable;
|
||||
|
||||
public SponsorBlockDataManager(final Context context) {
|
||||
final AppDatabase database = NewPipeDatabase.getInstance(context);
|
||||
sponsorBlockWhitelistTable = database.sponsorBlockWhitelistDAO();
|
||||
}
|
||||
|
||||
public Maybe<Long> addToWhitelist(final String uploader) {
|
||||
return Maybe.fromCallable(() -> {
|
||||
final SponsorBlockWhitelistEntry entry = new SponsorBlockWhitelistEntry(uploader);
|
||||
return sponsorBlockWhitelistTable.insert(entry);
|
||||
}).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Completable removeFromWhitelist(final String uploader) {
|
||||
return Completable.fromAction(() -> sponsorBlockWhitelistTable.deleteByUploader(uploader));
|
||||
}
|
||||
|
||||
public Single<Boolean> isWhiteListed(final String uploader) {
|
||||
return Single.fromCallable(() -> sponsorBlockWhitelistTable.exists(uploader))
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Completable clearWhitelist() {
|
||||
return Completable.fromAction(sponsorBlockWhitelistTable::deleteAll);
|
||||
}
|
||||
}
|
|
@ -46,6 +46,7 @@ import org.schabi.newpipe.util.Localization;
|
|||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.SponsorBlockHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -229,9 +230,12 @@ public final class PlayQueueActivity extends AppCompatActivity
|
|||
} else {
|
||||
onQueueUpdate(player.getPlayQueue());
|
||||
buildComponents();
|
||||
if (player != null) {
|
||||
player.setActivityListener(PlayQueueActivity.this);
|
||||
}
|
||||
player.setActivityListener(PlayQueueActivity.this);
|
||||
player.getCurrentStreamInfo().ifPresent(info ->
|
||||
SponsorBlockHelper.markSegments(
|
||||
getApplicationContext(),
|
||||
queueControlBinding.seekBar,
|
||||
info));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -57,6 +57,7 @@ import android.graphics.drawable.Drawable;
|
|||
import android.media.AudioManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -69,6 +70,7 @@ import com.google.android.exoplayer2.ExoPlayer;
|
|||
import com.google.android.exoplayer2.PlaybackException;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player.PositionInfo;
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.Tracks;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
|
@ -86,6 +88,8 @@ import org.schabi.newpipe.databinding.PlayerBinding;
|
|||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockAction;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockSegment;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
|
@ -118,6 +122,8 @@ import org.schabi.newpipe.player.ui.VideoPlayerUi;
|
|||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.SponsorBlockMode;
|
||||
import org.schabi.newpipe.util.SponsorBlockHelper;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.SerializedCache;
|
||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||
|
@ -262,7 +268,9 @@ public final class Player implements PlaybackListener, Listener {
|
|||
private final SharedPreferences prefs;
|
||||
@NonNull
|
||||
private final HistoryRecordManager recordManager;
|
||||
private SponsorBlockMode sponsorBlockMode = SponsorBlockMode.DISABLED;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
|
@ -273,6 +281,25 @@ public final class Player implements PlaybackListener, Listener {
|
|||
this.service = service;
|
||||
context = service;
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
final boolean isSponsorBlockEnabled = prefs.getBoolean(
|
||||
context.getString(R.string.sponsor_block_enable_key), false);
|
||||
|
||||
setSponsorBlockMode(isSponsorBlockEnabled
|
||||
? SponsorBlockMode.ENABLED
|
||||
: SponsorBlockMode.DISABLED);
|
||||
|
||||
preferenceChangeListener =
|
||||
(sharedPreferences, key) -> {
|
||||
if (context.getString(R.string.sponsor_block_enable_key).equals(key)) {
|
||||
setSponsorBlockMode(sharedPreferences.getBoolean(key, false)
|
||||
? SponsorBlockMode.ENABLED
|
||||
: SponsorBlockMode.DISABLED);
|
||||
}
|
||||
};
|
||||
|
||||
prefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
|
||||
|
||||
recordManager = new HistoryRecordManager(context);
|
||||
|
||||
setupBroadcastReceiver();
|
||||
|
@ -637,7 +664,7 @@ public final class Player implements PlaybackListener, Listener {
|
|||
}
|
||||
|
||||
if (playQueue != null) {
|
||||
playQueueManager = new MediaSourceManager(this, playQueue);
|
||||
playQueueManager = new MediaSourceManager(context, this, playQueue);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -923,12 +950,61 @@ public final class Player implements PlaybackListener, Listener {
|
|||
}
|
||||
|
||||
public void triggerProgressUpdate() {
|
||||
triggerProgressUpdate(false);
|
||||
}
|
||||
|
||||
public void triggerProgressUpdate(final boolean isRewind) {
|
||||
if (exoPlayerIsNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onUpdateProgress(Math.max((int) simpleExoPlayer.getCurrentPosition(), 0),
|
||||
(int) simpleExoPlayer.getDuration(), simpleExoPlayer.getBufferedPercentage());
|
||||
final int currentProgress = Math.max((int) simpleExoPlayer.getCurrentPosition(), 0);
|
||||
|
||||
onUpdateProgress(
|
||||
currentProgress,
|
||||
(int) simpleExoPlayer.getDuration(),
|
||||
simpleExoPlayer.getBufferedPercentage());
|
||||
|
||||
triggerCheckForSponsorBlockSegments(currentProgress, isRewind);
|
||||
}
|
||||
|
||||
private void triggerCheckForSponsorBlockSegments(final int currentProgress,
|
||||
final boolean isRewind) {
|
||||
if (sponsorBlockMode != SponsorBlockMode.ENABLED || !isPrepared) {
|
||||
return;
|
||||
}
|
||||
|
||||
getSkippableSponsorBlockSegment(currentProgress).ifPresent(sponsorBlockSegment -> {
|
||||
int skipTarget = isRewind
|
||||
? (int) Math.ceil((sponsorBlockSegment.startTime)) - 1
|
||||
: (int) Math.ceil((sponsorBlockSegment.endTime));
|
||||
|
||||
if (skipTarget < 0) {
|
||||
skipTarget = 0;
|
||||
}
|
||||
|
||||
// temporarily force EXACT seek parameters to prevent infinite skip looping
|
||||
final SeekParameters seekParams = simpleExoPlayer.getSeekParameters();
|
||||
simpleExoPlayer.setSeekParameters(SeekParameters.EXACT);
|
||||
|
||||
seekTo(skipTarget);
|
||||
|
||||
simpleExoPlayer.setSeekParameters(seekParams);
|
||||
|
||||
if (prefs.getBoolean(
|
||||
context.getString(R.string.sponsor_block_notifications_key), false)) {
|
||||
final String toastText =
|
||||
SponsorBlockHelper.convertCategoryToSkipMessage(
|
||||
context, sponsorBlockSegment.category);
|
||||
|
||||
Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d("SPONSOR_BLOCK", "Skipped segment: currentProgress = ["
|
||||
+ currentProgress + "], skipped to = [" + skipTarget + "]");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Disposable getProgressUpdateDisposable() {
|
||||
|
@ -1706,7 +1782,7 @@ public final class Player implements PlaybackListener, Listener {
|
|||
|
||||
public void fastForward() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "fastRewind() called");
|
||||
Log.d(TAG, "fastForward() called");
|
||||
}
|
||||
seekBy(retrieveSeekDurationFromPreferences(this));
|
||||
triggerProgressUpdate();
|
||||
|
@ -1717,7 +1793,7 @@ public final class Player implements PlaybackListener, Listener {
|
|||
Log.d(TAG, "fastRewind() called");
|
||||
}
|
||||
seekBy(-retrieveSeekDurationFromPreferences(this));
|
||||
triggerProgressUpdate();
|
||||
triggerProgressUpdate(true);
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
@ -2302,6 +2378,41 @@ public final class Player implements PlaybackListener, Listener {
|
|||
return Optional.ofNullable(fragmentListener);
|
||||
}
|
||||
|
||||
public SponsorBlockMode getSponsorBlockMode() {
|
||||
return sponsorBlockMode;
|
||||
}
|
||||
|
||||
public void setSponsorBlockMode(final SponsorBlockMode mode) {
|
||||
sponsorBlockMode = mode;
|
||||
}
|
||||
|
||||
public Optional<SponsorBlockSegment> getSkippableSponsorBlockSegment(final int progress) {
|
||||
return getCurrentStreamInfo().map(info -> {
|
||||
final SponsorBlockSegment[] sponsorBlockSegments = info.getSponsorBlockSegments();
|
||||
if (sponsorBlockSegments == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (final SponsorBlockSegment sponsorBlockSegment : sponsorBlockSegments) {
|
||||
if (sponsorBlockSegment.action != SponsorBlockAction.SKIP) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (progress < sponsorBlockSegment.startTime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (progress > sponsorBlockSegment.endTime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return sponsorBlockSegment;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the user interfaces connected with the player
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package org.schabi.newpipe.player;
|
||||
|
||||
public interface PlayerListener {
|
||||
void onPlayerPrepared(Player player);
|
||||
}
|
|
@ -1,5 +1,11 @@
|
|||
package org.schabi.newpipe.player.playback;
|
||||
|
||||
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.MediaSourceResolutionException;
|
||||
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.StreamInfoLoadException;
|
||||
import static org.schabi.newpipe.player.playqueue.PlayQueue.DEBUG;
|
||||
import static org.schabi.newpipe.util.ServiceHelper.getCacheExpirationMillis;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -38,11 +44,6 @@ import io.reactivex.rxjava3.internal.subscriptions.EmptySubscription;
|
|||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||
|
||||
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.MediaSourceResolutionException;
|
||||
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.StreamInfoLoadException;
|
||||
import static org.schabi.newpipe.player.playqueue.PlayQueue.DEBUG;
|
||||
import static org.schabi.newpipe.util.ServiceHelper.getCacheExpirationMillis;
|
||||
|
||||
public class MediaSourceManager {
|
||||
@NonNull
|
||||
private final String TAG = "MediaSourceManager@" + hashCode();
|
||||
|
@ -69,6 +70,8 @@ public class MediaSourceManager {
|
|||
*/
|
||||
private static final int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1;
|
||||
|
||||
@NonNull
|
||||
private final Context context;
|
||||
@NonNull
|
||||
private final PlaybackListener playbackListener;
|
||||
@NonNull
|
||||
|
@ -125,14 +128,16 @@ public class MediaSourceManager {
|
|||
|
||||
private final Handler removeMediaSourceHandler = new Handler();
|
||||
|
||||
public MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
public MediaSourceManager(@NonNull final Context context,
|
||||
@NonNull final PlaybackListener listener,
|
||||
@NonNull final PlayQueue playQueue) {
|
||||
this(listener, playQueue, 400L,
|
||||
this(context, listener, playQueue, 400L,
|
||||
/*playbackNearEndGapMillis=*/TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS),
|
||||
/*progressUpdateIntervalMillis*/TimeUnit.MILLISECONDS.convert(2, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
private MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
private MediaSourceManager(@NonNull final Context context,
|
||||
@NonNull final PlaybackListener listener,
|
||||
@NonNull final PlayQueue playQueue,
|
||||
final long loadDebounceMillis,
|
||||
final long playbackNearEndGapMillis,
|
||||
|
@ -146,6 +151,7 @@ public class MediaSourceManager {
|
|||
+ " ms] for them to be useful.");
|
||||
}
|
||||
|
||||
this.context = context;
|
||||
this.playbackListener = listener;
|
||||
this.playQueue = playQueue;
|
||||
|
||||
|
@ -420,7 +426,7 @@ public class MediaSourceManager {
|
|||
}
|
||||
|
||||
private Single<ManagedMediaSource> getLoadedMediaSource(@NonNull final PlayQueueItem stream) {
|
||||
return stream.getStream()
|
||||
return stream.getStream(context)
|
||||
.map(streamInfo -> Optional
|
||||
.ofNullable(playbackListener.sourceOf(stream, streamInfo))
|
||||
.<ManagedMediaSource>flatMap(source ->
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.schabi.newpipe.player.playqueue;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
@ -122,8 +124,8 @@ public class PlayQueueItem implements Serializable {
|
|||
}
|
||||
|
||||
@NonNull
|
||||
public Single<StreamInfo> getStream() {
|
||||
return ExtractorHelper.getStreamInfo(this.serviceId, this.url, false)
|
||||
public Single<StreamInfo> getStream(final Context context) {
|
||||
return ExtractorHelper.getStreamInfo(context, this.serviceId, this.url, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.doOnError(throwable -> error = throwable);
|
||||
}
|
||||
|
|
|
@ -127,6 +127,9 @@ public abstract class PlayerUi {
|
|||
public void onPrepared() {
|
||||
}
|
||||
|
||||
public void onMarkSeekbarRequested(@NonNull final StreamInfo streamInfo) {
|
||||
}
|
||||
|
||||
public void onBlocked() {
|
||||
}
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder;
|
|||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.SponsorBlockHelper;
|
||||
import org.schabi.newpipe.util.external_communication.KoreUtils;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.views.player.PlayerFastSeekOverlay;
|
||||
|
@ -797,6 +798,11 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
|||
binding.playbackSpeed.setText(formatSpeed(player.getPlaybackSpeed()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMarkSeekbarRequested(@NonNull final StreamInfo streamInfo) {
|
||||
SponsorBlockHelper.markSegments(context, binding.playbackSeekBar, streamInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlocked() {
|
||||
super.onBlocked();
|
||||
|
|
|
@ -63,6 +63,8 @@ public final class NewPipeSettings {
|
|||
PreferenceManager.setDefaultValues(context, R.xml.player_notification_settings, true);
|
||||
PreferenceManager.setDefaultValues(context, R.xml.update_settings, true);
|
||||
PreferenceManager.setDefaultValues(context, R.xml.debug_settings, true);
|
||||
PreferenceManager.setDefaultValues(context, R.xml.sponsor_block_settings, true);
|
||||
PreferenceManager.setDefaultValues(context, R.xml.sponsor_block_category_settings, true);
|
||||
|
||||
saveDefaultVideoDownloadDirectory(context);
|
||||
saveDefaultAudioDownloadDirectory(context);
|
||||
|
|
|
@ -41,6 +41,8 @@ public final class SettingsResourceRegistry {
|
|||
add(UpdateSettingsFragment.class, R.xml.update_settings);
|
||||
add(VideoAudioSettingsFragment.class, R.xml.video_audio_settings);
|
||||
add(ExoPlayerSettingsFragment.class, R.xml.exoplayer_settings);
|
||||
add(SponsorBlockSettingsFragment.class, R.xml.sponsor_block_settings);
|
||||
add(SponsorBlockCategoriesSettingsFragment.class, R.xml.sponsor_block_category_settings);
|
||||
}
|
||||
|
||||
private SettingRegistryEntry add(
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.ColorRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.settings.custom.EditColorPreference;
|
||||
|
||||
public class SponsorBlockCategoriesSettingsFragment extends BasePreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResourceRegistry();
|
||||
|
||||
final Preference allOnPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_all_on_key));
|
||||
allOnPreference.setOnPreferenceClickListener(p -> {
|
||||
final SwitchPreference sponsorCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_sponsor_key));
|
||||
final SwitchPreference introCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_intro_key));
|
||||
final SwitchPreference outroCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_outro_key));
|
||||
final SwitchPreference interactionCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_interaction_key));
|
||||
final SwitchPreference highlightCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_highlight_key));
|
||||
final SwitchPreference selfPromoCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_self_promo_key));
|
||||
final SwitchPreference nonMusicCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_non_music_key));
|
||||
final SwitchPreference previewCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_preview_key));
|
||||
final SwitchPreference fillerCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_filler_key));
|
||||
|
||||
sponsorCategoryPreference.setChecked(true);
|
||||
introCategoryPreference.setChecked(true);
|
||||
outroCategoryPreference.setChecked(true);
|
||||
interactionCategoryPreference.setChecked(true);
|
||||
highlightCategoryPreference.setChecked(true);
|
||||
selfPromoCategoryPreference.setChecked(true);
|
||||
nonMusicCategoryPreference.setChecked(true);
|
||||
previewCategoryPreference.setChecked(true);
|
||||
fillerCategoryPreference.setChecked(true);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
final Preference allOffPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_all_off_key));
|
||||
allOffPreference.setOnPreferenceClickListener(p -> {
|
||||
final SwitchPreference sponsorCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_sponsor_key));
|
||||
final SwitchPreference introCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_intro_key));
|
||||
final SwitchPreference outroCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_outro_key));
|
||||
final SwitchPreference interactionCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_interaction_key));
|
||||
final SwitchPreference highlightCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_highlight_key));
|
||||
final SwitchPreference selfPromoCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_self_promo_key));
|
||||
final SwitchPreference nonMusicCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_non_music_key));
|
||||
final SwitchPreference previewCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_preview_key));
|
||||
final SwitchPreference fillerCategoryPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_filler_key));
|
||||
|
||||
sponsorCategoryPreference.setChecked(false);
|
||||
introCategoryPreference.setChecked(false);
|
||||
outroCategoryPreference.setChecked(false);
|
||||
interactionCategoryPreference.setChecked(false);
|
||||
highlightCategoryPreference.setChecked(false);
|
||||
selfPromoCategoryPreference.setChecked(false);
|
||||
nonMusicCategoryPreference.setChecked(false);
|
||||
previewCategoryPreference.setChecked(false);
|
||||
fillerCategoryPreference.setChecked(false);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
final Preference resetPreference =
|
||||
findPreference(getString(R.string.sponsor_block_category_reset_key));
|
||||
resetPreference.setOnPreferenceClickListener(p -> {
|
||||
new AlertDialog.Builder(p.getContext())
|
||||
.setMessage(R.string.sponsor_block_confirm_reset_colors)
|
||||
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||
final SharedPreferences.Editor editor =
|
||||
getPreferenceManager()
|
||||
.getSharedPreferences()
|
||||
.edit();
|
||||
|
||||
setColorPreference(editor,
|
||||
R.string.sponsor_block_category_sponsor_color_key,
|
||||
R.color.sponsor_segment);
|
||||
setColorPreference(editor,
|
||||
R.string.sponsor_block_category_intro_color_key,
|
||||
R.color.intro_segment);
|
||||
setColorPreference(editor,
|
||||
R.string.sponsor_block_category_outro_color_key,
|
||||
R.color.outro_segment);
|
||||
setColorPreference(editor,
|
||||
R.string.sponsor_block_category_interaction_color_key,
|
||||
R.color.interaction_segment);
|
||||
setColorPreference(editor,
|
||||
R.string.sponsor_block_category_highlight_color_key,
|
||||
R.color.highlight_segment);
|
||||
setColorPreference(editor,
|
||||
R.string.sponsor_block_category_self_promo_color_key,
|
||||
R.color.self_promo_segment);
|
||||
setColorPreference(editor,
|
||||
R.string.sponsor_block_category_non_music_color_key,
|
||||
R.color.non_music_segment);
|
||||
setColorPreference(editor,
|
||||
R.string.sponsor_block_category_preview_color_key,
|
||||
R.color.preview_segment);
|
||||
setColorPreference(editor,
|
||||
R.string.sponsor_block_category_filler_color_key,
|
||||
R.color.filler_segment);
|
||||
setColorPreference(editor,
|
||||
R.string.sponsor_block_category_pending_color_key,
|
||||
R.color.pending_segment);
|
||||
|
||||
editor.apply();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss())
|
||||
.show();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void setColorPreference(final SharedPreferences.Editor editor,
|
||||
@StringRes final int resId,
|
||||
@ColorRes final int colorId) {
|
||||
final String colorStr = "#" + Integer.toHexString(getResources().getColor(colorId));
|
||||
editor.putString(getString(resId), colorStr);
|
||||
final EditColorPreference colorPreference = findPreference(getString(resId));
|
||||
colorPreference.setText(colorStr);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.local.sponsorblock.SponsorBlockDataManager;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class SponsorBlockSettingsFragment extends BasePreferenceFragment {
|
||||
private SponsorBlockDataManager sponsorBlockDataManager;
|
||||
private Disposable workerClearWhitelist;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
sponsorBlockDataManager = new SponsorBlockDataManager(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
if (workerClearWhitelist != null) {
|
||||
workerClearWhitelist.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResourceRegistry();
|
||||
|
||||
final Preference sponsorBlockWebsitePreference =
|
||||
findPreference(getString(R.string.sponsor_block_home_page_key));
|
||||
assert sponsorBlockWebsitePreference != null;
|
||||
sponsorBlockWebsitePreference.setOnPreferenceClickListener((Preference p) -> {
|
||||
final Intent i = new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse(getString(R.string.sponsor_block_homepage_url)));
|
||||
startActivity(i);
|
||||
return true;
|
||||
});
|
||||
|
||||
final Preference sponsorBlockPrivacyPreference =
|
||||
findPreference(getString(R.string.sponsor_block_privacy_key));
|
||||
assert sponsorBlockPrivacyPreference != null;
|
||||
sponsorBlockPrivacyPreference.setOnPreferenceClickListener((Preference p) -> {
|
||||
final Intent i = new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse(getString(R.string.sponsor_block_privacy_policy_url)));
|
||||
startActivity(i);
|
||||
return true;
|
||||
});
|
||||
|
||||
final Preference sponsorBlockClearWhitelistPreference =
|
||||
findPreference(getString(R.string.sponsor_block_clear_whitelist_key));
|
||||
assert sponsorBlockClearWhitelistPreference != null;
|
||||
sponsorBlockClearWhitelistPreference.setOnPreferenceClickListener((Preference p) -> {
|
||||
new AlertDialog.Builder(p.getContext())
|
||||
.setMessage(R.string.sponsor_block_confirm_clear_whitelist)
|
||||
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||
workerClearWhitelist =
|
||||
sponsorBlockDataManager.clearWhitelist()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
Toast.makeText(p.getContext(),
|
||||
R.string.sponsor_block_whitelist_cleared_toast,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}, error -> {
|
||||
// TODO
|
||||
});
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss())
|
||||
.show();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package org.schabi.newpipe.settings.custom;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class EditColorPreference extends EditTextPreference
|
||||
implements Preference.OnPreferenceChangeListener {
|
||||
private PreferenceViewHolder viewHolder;
|
||||
|
||||
public EditColorPreference(final Context context, final AttributeSet attrs,
|
||||
final int defStyleAttr, final int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
init();
|
||||
}
|
||||
|
||||
public EditColorPreference(final Context context, final AttributeSet attrs,
|
||||
final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
public EditColorPreference(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public EditColorPreference(final Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setWidgetLayoutResource(R.layout.preference_edit_color);
|
||||
setOnPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
|
||||
viewHolder = holder;
|
||||
|
||||
final String colorStr =
|
||||
getPreferenceManager()
|
||||
.getSharedPreferences()
|
||||
.getString(getKey(), null);
|
||||
|
||||
if (colorStr == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int color = Color.parseColor(colorStr);
|
||||
|
||||
final View view = viewHolder.findViewById(R.id.sponsor_block_segment_color_view);
|
||||
view.setBackgroundColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(final Preference preference, final Object newValue) {
|
||||
try {
|
||||
final int color = Color.parseColor((String) newValue);
|
||||
|
||||
final View view = viewHolder.findViewById(R.id.sponsor_block_segment_color_view);
|
||||
view.setBackgroundColor(color);
|
||||
|
||||
return true;
|
||||
} catch (final Exception e) {
|
||||
Toast.makeText(getContext(), R.string.invalid_color_toast, Toast.LENGTH_SHORT).show();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package org.schabi.newpipe.settings.custom;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.TypedArray;
|
||||
import android.net.Uri;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class SponsorBlockApiUrlPreference extends Preference {
|
||||
public SponsorBlockApiUrlPreference(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
@Override
|
||||
protected void onSetInitialValue(@Nullable final Object defaultValue) {
|
||||
// apparently this is how you're supposed to respect default values for a custom preference
|
||||
persistString(getPersistedString((String) defaultValue));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Object onGetDefaultValue(@NonNull final TypedArray a, final int index) {
|
||||
return a.getString(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick() {
|
||||
super.onClick();
|
||||
|
||||
final Context context = getContext();
|
||||
|
||||
final String apiUrl = getPersistedString(null);
|
||||
|
||||
final View alertDialogView = LayoutInflater.from(context)
|
||||
.inflate(R.layout.dialog_sponsor_block_api_url, null);
|
||||
|
||||
final EditText editText = alertDialogView.findViewById(R.id.api_url_edit);
|
||||
editText.setText(apiUrl);
|
||||
editText.setOnFocusChangeListener((v, hasFocus) -> editText.post(() -> {
|
||||
final InputMethodManager inputMethodManager = (InputMethodManager) context
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
inputMethodManager
|
||||
.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
|
||||
}));
|
||||
editText.requestFocus();
|
||||
|
||||
alertDialogView.findViewById(R.id.icon_api_url_help)
|
||||
.setOnClickListener(v -> {
|
||||
final Uri privacyPolicyUri = Uri.parse(context
|
||||
.getString(R.string.sponsor_block_privacy_policy_url));
|
||||
final View helpDialogView = LayoutInflater.from(context)
|
||||
.inflate(R.layout.dialog_sponsor_block_api_url_help, null);
|
||||
final View privacyPolicyButton = helpDialogView
|
||||
.findViewById(R.id.sponsor_block_privacy_policy_button);
|
||||
privacyPolicyButton.setOnClickListener(v1 -> {
|
||||
final Intent i = new Intent(Intent.ACTION_VIEW, privacyPolicyUri);
|
||||
context.startActivity(i);
|
||||
});
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setView(helpDialogView)
|
||||
.setPositiveButton("Use Official", (dialog, which) -> {
|
||||
editText.setText(context
|
||||
.getString(R.string.sponsor_block_default_api_url));
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNeutralButton("Close", (dialog, which) -> dialog.dismiss())
|
||||
.create()
|
||||
.show();
|
||||
});
|
||||
|
||||
final AlertDialog alertDialog =
|
||||
new AlertDialog.Builder(context)
|
||||
.setView(alertDialogView)
|
||||
.setTitle(context.getString(R.string.sponsor_block_api_url_title))
|
||||
.setPositiveButton("OK", (dialog, which) -> {
|
||||
final String newValue = editText.getText().toString();
|
||||
if (!newValue.isEmpty()) {
|
||||
final SharedPreferences.Editor editor =
|
||||
getPreferenceManager().getSharedPreferences().edit();
|
||||
editor.putString(getKey(), newValue);
|
||||
editor.apply();
|
||||
|
||||
callChangeListener(newValue);
|
||||
}
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel())
|
||||
.create();
|
||||
|
||||
alertDialog.show();
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
|||
import static org.schabi.newpipe.util.text.TextLinkifier.SET_LINK_MOVEMENT_METHOD;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
@ -47,6 +48,7 @@ import org.schabi.newpipe.extractor.kiosk.KioskInfo;
|
|||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockApiSettings;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
|
||||
|
@ -110,11 +112,16 @@ public final class ExtractorHelper {
|
|||
});
|
||||
}
|
||||
|
||||
public static Single<StreamInfo> getStreamInfo(final int serviceId, final String url,
|
||||
public static Single<StreamInfo> getStreamInfo(final Context context,
|
||||
final int serviceId,
|
||||
final String url,
|
||||
final boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.STREAM,
|
||||
Single.fromCallable(() -> StreamInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
Single.fromCallable(() -> StreamInfo.getInfo(
|
||||
NewPipe.getService(serviceId),
|
||||
url,
|
||||
buildSponsorBlockApiSettings(context))));
|
||||
}
|
||||
|
||||
public static Single<ChannelInfo> getChannelInfo(final int serviceId, final String url,
|
||||
|
@ -334,4 +341,49 @@ public final class ExtractorHelper {
|
|||
return text.substring(0, 1).toUpperCase() + text.substring(1).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable SponsorBlockApiSettings buildSponsorBlockApiSettings(
|
||||
final Context context) {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
final boolean isSponsorBlockEnabled = prefs.getBoolean(context
|
||||
.getString(R.string.sponsor_block_enable_key), false);
|
||||
|
||||
if (!isSponsorBlockEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final SponsorBlockApiSettings result = new SponsorBlockApiSettings();
|
||||
result.apiUrl =
|
||||
prefs.getString(context.getString(R.string.sponsor_block_api_url_key), null);
|
||||
result.includeSponsorCategory =
|
||||
prefs.getBoolean(context
|
||||
.getString(R.string.sponsor_block_category_sponsor_key), false);
|
||||
result.includeIntroCategory =
|
||||
prefs.getBoolean(context
|
||||
.getString(R.string.sponsor_block_category_intro_key), false);
|
||||
result.includeOutroCategory =
|
||||
prefs.getBoolean(context
|
||||
.getString(R.string.sponsor_block_category_outro_key), false);
|
||||
result.includeInteractionCategory =
|
||||
prefs.getBoolean(context
|
||||
.getString(R.string.sponsor_block_category_interaction_key), false);
|
||||
result.includeHighlightCategory =
|
||||
prefs.getBoolean(context
|
||||
.getString(R.string.sponsor_block_category_highlight_key), false);
|
||||
result.includeSelfPromoCategory =
|
||||
prefs.getBoolean(context
|
||||
.getString(R.string.sponsor_block_category_self_promo_key), false);
|
||||
result.includeMusicCategory =
|
||||
prefs.getBoolean(context
|
||||
.getString(R.string.sponsor_block_category_non_music_key), false);
|
||||
result.includePreviewCategory =
|
||||
prefs.getBoolean(context
|
||||
.getString(R.string.sponsor_block_category_preview_key), false);
|
||||
result.includeFillerCategory =
|
||||
prefs.getBoolean(context
|
||||
.getString(R.string.sponsor_block_category_filler_key), false);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ public final class SparseItemUtil {
|
|||
@NonNull final String url,
|
||||
final Consumer<StreamInfo> callback) {
|
||||
Toast.makeText(context, R.string.loading_stream_details, Toast.LENGTH_SHORT).show();
|
||||
ExtractorHelper.getStreamInfo(serviceId, url, false)
|
||||
ExtractorHelper.getStreamInfo(context, serviceId, url, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(result -> {
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockCategory;
|
||||
import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockSegment;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.views.MarkableSeekBar;
|
||||
import org.schabi.newpipe.views.SeekBarMarker;
|
||||
|
||||
public final class SponsorBlockHelper {
|
||||
|
||||
|
||||
private SponsorBlockHelper() {
|
||||
}
|
||||
|
||||
public static Integer convertCategoryToColor(
|
||||
final SponsorBlockCategory category,
|
||||
final Context context
|
||||
) {
|
||||
final String key;
|
||||
final String colorStr;
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
switch (category) {
|
||||
case SPONSOR -> {
|
||||
key = context.getString(R.string.sponsor_block_category_sponsor_color_key);
|
||||
colorStr = prefs.getString(key, null);
|
||||
return colorStr == null
|
||||
? context.getResources().getColor(R.color.sponsor_segment)
|
||||
: Color.parseColor(colorStr);
|
||||
}
|
||||
case INTRO -> {
|
||||
key = context.getString(R.string.sponsor_block_category_intro_color_key);
|
||||
colorStr = prefs.getString(key, null);
|
||||
return colorStr == null
|
||||
? context.getResources().getColor(R.color.intro_segment)
|
||||
: Color.parseColor(colorStr);
|
||||
}
|
||||
case OUTRO -> {
|
||||
key = context.getString(R.string.sponsor_block_category_outro_color_key);
|
||||
colorStr = prefs.getString(key, null);
|
||||
return colorStr == null
|
||||
? context.getResources().getColor(R.color.outro_segment)
|
||||
: Color.parseColor(colorStr);
|
||||
}
|
||||
case INTERACTION -> {
|
||||
key = context.getString(R.string.sponsor_block_category_interaction_color_key);
|
||||
colorStr = prefs.getString(key, null);
|
||||
return colorStr == null
|
||||
? context.getResources().getColor(R.color.interaction_segment)
|
||||
: Color.parseColor(colorStr);
|
||||
}
|
||||
case HIGHLIGHT -> {
|
||||
key = context.getString(R.string.sponsor_block_category_highlight_color_key);
|
||||
colorStr = prefs.getString(key, null);
|
||||
return colorStr == null
|
||||
? context.getResources().getColor(R.color.highlight_segment)
|
||||
: Color.parseColor(colorStr);
|
||||
}
|
||||
case SELF_PROMO -> {
|
||||
key = context.getString(R.string.sponsor_block_category_self_promo_color_key);
|
||||
colorStr = prefs.getString(key, null);
|
||||
return colorStr == null
|
||||
? context.getResources().getColor(R.color.self_promo_segment)
|
||||
: Color.parseColor(colorStr);
|
||||
}
|
||||
case NON_MUSIC -> {
|
||||
key = context.getString(R.string.sponsor_block_category_non_music_color_key);
|
||||
colorStr = prefs.getString(key, null);
|
||||
return colorStr == null
|
||||
? context.getResources().getColor(R.color.non_music_segment)
|
||||
: Color.parseColor(colorStr);
|
||||
}
|
||||
case PREVIEW -> {
|
||||
key = context.getString(R.string.sponsor_block_category_preview_color_key);
|
||||
colorStr = prefs.getString(key, null);
|
||||
return colorStr == null
|
||||
? context.getResources().getColor(R.color.preview_segment)
|
||||
: Color.parseColor(colorStr);
|
||||
}
|
||||
case FILLER -> {
|
||||
key = context.getString(R.string.sponsor_block_category_filler_color_key);
|
||||
colorStr = prefs.getString(key, null);
|
||||
return colorStr == null
|
||||
? context.getResources().getColor(R.color.filler_segment)
|
||||
: Color.parseColor(colorStr);
|
||||
}
|
||||
case PENDING -> {
|
||||
key = context.getString(R.string.sponsor_block_category_pending_color_key);
|
||||
colorStr = prefs.getString(key, null);
|
||||
return colorStr == null
|
||||
? context.getResources().getColor(R.color.pending_segment)
|
||||
: Color.parseColor(colorStr);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void markSegments(
|
||||
final Context context,
|
||||
final MarkableSeekBar seekBar,
|
||||
@NonNull final StreamInfo streamInfo
|
||||
) {
|
||||
seekBar.clearMarkers();
|
||||
|
||||
final SponsorBlockSegment[] sponsorBlockSegments = streamInfo.getSponsorBlockSegments();
|
||||
|
||||
if (sponsorBlockSegments == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final SponsorBlockSegment sponsorBlockSegment : sponsorBlockSegments) {
|
||||
final Integer color = convertCategoryToColor(
|
||||
sponsorBlockSegment.category, context);
|
||||
|
||||
// if null, then this category should not be marked
|
||||
if (color == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// duration is in seconds, we need milliseconds
|
||||
final long length = streamInfo.getDuration() * 1000;
|
||||
|
||||
final SeekBarMarker seekBarMarker =
|
||||
new SeekBarMarker(sponsorBlockSegment.startTime, sponsorBlockSegment.endTime,
|
||||
length, color);
|
||||
seekBar.seekBarMarkers.add(seekBarMarker);
|
||||
}
|
||||
|
||||
seekBar.drawMarkers();
|
||||
}
|
||||
|
||||
public static String convertCategoryToFriendlyName(final Context context,
|
||||
final SponsorBlockCategory category) {
|
||||
return switch (category) {
|
||||
case SPONSOR -> context.getString(
|
||||
R.string.sponsor_block_category_sponsor);
|
||||
case INTRO -> context.getString(
|
||||
R.string.sponsor_block_category_intro);
|
||||
case OUTRO -> context.getString(
|
||||
R.string.sponsor_block_category_outro);
|
||||
case INTERACTION -> context.getString(
|
||||
R.string.sponsor_block_category_interaction);
|
||||
case HIGHLIGHT -> context.getString(
|
||||
R.string.sponsor_block_category_highlight);
|
||||
case SELF_PROMO -> context.getString(
|
||||
R.string.sponsor_block_category_self_promo);
|
||||
case NON_MUSIC -> context.getString(
|
||||
R.string.sponsor_block_category_non_music);
|
||||
case PREVIEW -> context.getString(
|
||||
R.string.sponsor_block_category_preview);
|
||||
case FILLER -> context.getString(
|
||||
R.string.sponsor_block_category_filler);
|
||||
case PENDING -> context.getString(
|
||||
R.string.sponsor_block_category_pending);
|
||||
};
|
||||
}
|
||||
|
||||
public static String convertCategoryToSkipMessage(final Context context,
|
||||
final SponsorBlockCategory category) {
|
||||
return switch (category) {
|
||||
case SPONSOR -> context
|
||||
.getString(R.string.sponsor_block_skip_sponsor_toast);
|
||||
case INTRO -> context
|
||||
.getString(R.string.sponsor_block_skip_intro_toast);
|
||||
case OUTRO -> context
|
||||
.getString(R.string.sponsor_block_skip_outro_toast);
|
||||
case INTERACTION -> context
|
||||
.getString(R.string.sponsor_block_skip_interaction_toast);
|
||||
case HIGHLIGHT -> ""; // this should never happen
|
||||
case SELF_PROMO -> context
|
||||
.getString(R.string.sponsor_block_skip_self_promo_toast);
|
||||
case NON_MUSIC -> context
|
||||
.getString(R.string.sponsor_block_skip_non_music_toast);
|
||||
case PREVIEW -> context
|
||||
.getString(R.string.sponsor_block_skip_preview_toast);
|
||||
case FILLER -> context
|
||||
.getString(R.string.sponsor_block_skip_filler_toast);
|
||||
case PENDING -> context
|
||||
.getString(R.string.sponsor_block_skip_pending_toast);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
public enum SponsorBlockMode {
|
||||
DISABLED,
|
||||
ENABLED,
|
||||
IGNORE
|
||||
}
|
17
app/src/main/java/org/schabi/newpipe/util/TimeUtils.java
Normal file
17
app/src/main/java/org/schabi/newpipe/util/TimeUtils.java
Normal file
|
@ -0,0 +1,17 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public final class TimeUtils {
|
||||
private TimeUtils() {
|
||||
}
|
||||
|
||||
public static String millisecondsToString(final double milliseconds) {
|
||||
final int seconds = (int) (milliseconds / 1000) % 60;
|
||||
final int minutes = (int) ((milliseconds / (1000 * 60)) % 60);
|
||||
final int hours = (int) ((milliseconds / (1000 * 60 * 60)) % 24);
|
||||
|
||||
return String.format(Locale.getDefault(),
|
||||
"%02d:%02d:%02d", hours, minutes, seconds);
|
||||
}
|
||||
}
|
|
@ -154,7 +154,7 @@ public final class InternalUrlsHandler {
|
|||
}
|
||||
|
||||
final Single<StreamInfo> single =
|
||||
ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false);
|
||||
ExtractorHelper.getStreamInfo(context, service.getServiceId(), cleanUrl, false);
|
||||
disposables.add(single.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(info -> {
|
||||
|
|
|
@ -24,8 +24,6 @@ import android.view.KeyEvent;
|
|||
import android.view.ViewTreeObserver;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatSeekBar;
|
||||
|
||||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
|
||||
/**
|
||||
|
@ -33,7 +31,7 @@ import org.schabi.newpipe.util.DeviceUtils;
|
|||
* (onStartTrackingTouch/onStopTrackingTouch), so existing code does not need to be changed to
|
||||
* work with it.
|
||||
*/
|
||||
public final class FocusAwareSeekBar extends AppCompatSeekBar {
|
||||
public final class FocusAwareSeekBar extends MarkableSeekBar {
|
||||
private NestedListener listener;
|
||||
|
||||
private ViewTreeObserver treeObserver;
|
||||
|
|
109
app/src/main/java/org/schabi/newpipe/views/MarkableSeekBar.java
Normal file
109
app/src/main/java/org/schabi/newpipe/views/MarkableSeekBar.java
Normal file
|
@ -0,0 +1,109 @@
|
|||
package org.schabi.newpipe.views;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatSeekBar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MarkableSeekBar extends AppCompatSeekBar {
|
||||
public ArrayList<SeekBarMarker> seekBarMarkers = new ArrayList<>();
|
||||
private Drawable originalProgressDrawable;
|
||||
|
||||
public MarkableSeekBar(final Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public MarkableSeekBar(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public MarkableSeekBar(final Context context,
|
||||
final AttributeSet attrs,
|
||||
final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgressDrawable(final Drawable d) {
|
||||
super.setProgressDrawable(d);
|
||||
|
||||
// stored for when we draw (and potentially re-draw) markers
|
||||
originalProgressDrawable = d;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(final int w, final int h, final int oldW, final int oldH) {
|
||||
super.onSizeChanged(w, h, oldW, oldH);
|
||||
|
||||
// re-draw markers since the progress bar may have a different width
|
||||
drawMarkers();
|
||||
}
|
||||
|
||||
public void drawMarkers() {
|
||||
if (seekBarMarkers.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Markers are drawn like so:
|
||||
//
|
||||
// - LayerDrawable (original drawable for the SeekBar)
|
||||
// - GradientDrawable (background)
|
||||
// - ScaleDrawable (secondaryProgress)
|
||||
// - ScaleDrawable (progress)
|
||||
// - LayerDrawable (we add our markers in a sub-LayerDrawable)
|
||||
// - Drawable (marker)
|
||||
// - Drawable (marker)
|
||||
// - Drawable (marker)
|
||||
// - etc...
|
||||
|
||||
final int width = getMeasuredWidth() - (getPaddingStart() + getPaddingEnd());
|
||||
|
||||
LayerDrawable layerDrawable = (LayerDrawable) originalProgressDrawable;
|
||||
|
||||
final ArrayList<Drawable> markerDrawables = new ArrayList<>();
|
||||
markerDrawables.add(layerDrawable);
|
||||
|
||||
for (final SeekBarMarker seekBarMarker : seekBarMarkers) {
|
||||
@SuppressLint("PrivateResource")
|
||||
final Drawable markerDrawable =
|
||||
ContextCompat.getDrawable(
|
||||
getContext(),
|
||||
R.drawable.abc_scrubber_primary_mtrl_alpha);
|
||||
|
||||
final PorterDuffColorFilter colorFilter =
|
||||
new PorterDuffColorFilter(seekBarMarker.color, PorterDuff.Mode.SRC_IN);
|
||||
|
||||
assert markerDrawable != null;
|
||||
markerDrawable.setColorFilter(colorFilter);
|
||||
|
||||
markerDrawables.add(markerDrawable);
|
||||
}
|
||||
|
||||
layerDrawable = new LayerDrawable(markerDrawables.toArray(new Drawable[0]));
|
||||
|
||||
for (int i = 1; i < layerDrawable.getNumberOfLayers(); i++) {
|
||||
final SeekBarMarker seekBarMarker = seekBarMarkers.get(i - 1);
|
||||
final int l = (int) (width * seekBarMarker.percentStart);
|
||||
final int r = (int) (width * (1.0 - seekBarMarker.percentEnd));
|
||||
|
||||
layerDrawable.setLayerInset(i, l, 0, r, 0);
|
||||
}
|
||||
|
||||
super.setProgressDrawable(layerDrawable);
|
||||
}
|
||||
|
||||
public void clearMarkers() {
|
||||
seekBarMarkers.clear();
|
||||
super.setProgressDrawable(originalProgressDrawable);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package org.schabi.newpipe.views;
|
||||
|
||||
public class SeekBarMarker {
|
||||
public double startTime;
|
||||
public double endTime;
|
||||
public double percentStart;
|
||||
public double percentEnd;
|
||||
public int color;
|
||||
|
||||
public SeekBarMarker(final double startTime,
|
||||
final double endTime,
|
||||
final long maxTime,
|
||||
final int color) {
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
this.percentStart = ((startTime / maxTime) * 100.0) / 100.0;
|
||||
this.percentEnd = ((endTime / maxTime) * 100.0) / 100.0;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public SeekBarMarker(final double percentStart, final double percentEnd, final int color) {
|
||||
this.percentStart = percentStart;
|
||||
this.percentEnd = percentEnd;
|
||||
this.color = color;
|
||||
}
|
||||
}
|
10
app/src/main/res/drawable/ic_chevron_left.xml
Normal file
10
app/src/main/res/drawable/ic_chevron_left.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/defaultIconTint"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M17.77,3.77l-1.77,-1.77l-10,10l10,10l1.77,-1.77l-8.23,-8.23z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_chevron_right.xml
Normal file
10
app/src/main/res/drawable/ic_chevron_right.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/defaultIconTint"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M6.23,20.23l1.77,1.77l10,-10l-10,-10l-1.77,1.77l8.23,8.23z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_fast_forward.xml
Normal file
10
app/src/main/res/drawable/ic_fast_forward.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/defaultIconTint"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_fast_rewind.xml
Normal file
10
app/src/main/res/drawable/ic_fast_rewind.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/defaultIconTint"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M11,18L11,6l-8.5,6 8.5,6zM11.5,12l8.5,6L20,6l-8.5,6z" />
|
||||
</vector>
|
18
app/src/main/res/drawable/ic_sponsor_block_disable.xml
Normal file
18
app/src/main/res/drawable/ic_sponsor_block_disable.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,22.7994C11.55,22.7994 11.1,22.7094 10.74,22.4394 4.89,18.8394 1.29,12.6294 1.2,5.7894 1.2,4.8894 1.65,3.9894 2.46,3.5394 8.4,0.3894 15.6,0.3894 21.54,3.6294 22.35,3.9894 22.8,4.8894 22.8,5.7894 22.71,12.6294 19.11,18.8394 13.35,22.4394 12.9,22.7094 12.45,22.7994 12,22.7994ZM12,1.9194c-3.15,0 -6.3,0.81 -9.18,2.34 -0.54,0.27 -0.9,0.9 -0.9,1.53 0.09,6.57 3.51,12.51 9.18,16.02 0.54,0.36 1.26,0.36 1.8,0C18.57,18.3894 21.9,12.3594 22.08,5.7894 22.08,5.1594 21.72,4.5294 21.18,4.2594 18.3,2.7294 15.15,1.9194 12,1.9194Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M20.73,4.9794C15.24,2.0994 8.76,2.0994 3.27,4.9794 3,5.1594 2.82,5.4294 2.82,5.7894c0.09,6.48 3.51,12.06 8.73,15.3 0.27,0.18 0.63,0.18 0.9,0 5.13,-3.15 8.64,-8.82 8.73,-15.3C21.18,5.4294 21,5.1594 20.73,4.9794ZM12,15.8694c-2.79,0 -4.95,-2.25 -4.95,-4.95 0,-2.7 2.25,-4.95 4.95,-4.95 2.7,0 4.95,2.25 4.95,4.95 0,2.79 -2.16,4.95 -4.95,4.95z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m15.15,13.4394c0.54,-0.72 0.9,-1.53 0.9,-2.52 0,-2.25 -1.8,-4.05 -4.05,-4.05 -0.9,0 -1.8,0.36 -2.52,0.9z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m8.85,8.4894c-0.54,0.72 -0.9,1.53 -0.9,2.52 0,2.25 1.8,4.05 4.05,4.05 0.9,0 1.8,-0.36 2.52,-0.9z"/>
|
||||
</vector>
|
13
app/src/main/res/drawable/ic_sponsor_block_enable.xml
Normal file
13
app/src/main/res/drawable/ic_sponsor_block_enable.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/defaultIconTint"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,22.7994C11.55,22.7994 11.1,22.7094 10.74,22.4394 4.89,18.8394 1.29,12.6294 1.2,5.7894 1.2,4.8894 1.65,3.9894 2.46,3.5394 8.4,0.3894 15.6,0.3894 21.54,3.6294 22.35,3.9894 22.8,4.8894 22.8,5.7894 22.71,12.6294 19.11,18.8394 13.35,22.4394 12.9,22.7094 12.45,22.7994 12,22.7994ZM12,1.9194c-3.15,0 -6.3,0.81 -9.18,2.34 -0.54,0.27 -0.9,0.9 -0.9,1.53 0.09,6.57 3.51,12.51 9.18,16.02 0.54,0.36 1.26,0.36 1.8,0C18.57,18.3894 21.9,12.3594 22.08,5.7894 22.08,5.1594 21.72,4.5294 21.18,4.2594 18.3,2.7294 15.15,1.9194 12,1.9194Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M20.73,4.9794C15.24,2.0994 8.76,2.0994 3.27,4.9794 3,5.1594 2.82,5.4294 2.82,5.7894c0.09,6.48 3.51,12.06 8.73,15.3 0.27,0.18 0.63,0.18 0.9,0 5.13,-3.15 8.64,-8.82 8.73,-15.3C21.18,5.4294 21,5.1594 20.73,4.9794ZM9.66,15.1494L9.66,6.7794l7.29,4.23z"/>
|
||||
</vector>
|
12
app/src/main/res/drawable/ic_sponsor_block_exclude.xml
Normal file
12
app/src/main/res/drawable/ic_sponsor_block_exclude.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,22.7994C11.55,22.7994 11.1,22.7094 10.74,22.4394 4.89,18.8394 1.29,12.6294 1.2,5.7894 1.2,4.8894 1.65,3.9894 2.46,3.5394 8.4,0.3894 15.6,0.3894 21.54,3.6294 22.35,3.9894 22.8,4.8894 22.8,5.7894 22.71,12.6294 19.11,18.8394 13.35,22.4394 12.9,22.7094 12.45,22.7994 12,22.7994ZM12,1.9194c-3.15,0 -6.3,0.81 -9.18,2.34 -0.54,0.27 -0.9,0.9 -0.9,1.53 0.09,6.57 3.51,12.51 9.18,16.02 0.54,0.36 1.26,0.36 1.8,0C18.57,18.3894 21.9,12.3594 22.08,5.7894 22.08,5.1594 21.72,4.5294 21.18,4.2594 18.3,2.7294 15.15,1.9194 12,1.9194Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M20.73,4.9794C15.24,2.0994 8.76,2.0994 3.27,4.9794 3,5.1594 2.82,5.4294 2.82,5.7894c0.09,6.48 3.51,12.06 8.73,15.3 0.27,0.18 0.63,0.18 0.9,0 5.13,-3.15 8.64,-8.82 8.73,-15.3C21.18,5.4294 21,5.1594 20.73,4.9794ZM12,15.4194c0,0 -4.5,-3.6 -4.5,-5.94 0,-1.53 0.99,-2.43 2.25,-2.43 1.08,0 2.25,1.17 2.25,1.17 0,0 1.08,-1.17 2.25,-1.17 1.26,0 2.25,0.81 2.25,2.43 0,2.34 -4.5,5.94 -4.5,5.94z"/>
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_upload.xml
Normal file
10
app/src/main/res/drawable/ic_upload.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/defaultIconTint"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z" />
|
||||
</vector>
|
|
@ -280,7 +280,7 @@
|
|||
tools:ignore="HardcodedText"
|
||||
tools:text="1:06:29" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<org.schabi.newpipe.views.FocusAwareSeekBar
|
||||
android:id="@+id/seek_bar"
|
||||
style="@style/Widget.AppCompat.SeekBar"
|
||||
android:layout_width="0dp"
|
||||
|
|
|
@ -116,7 +116,7 @@
|
|||
tools:text="1:06:29" />
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<org.schabi.newpipe.views.FocusAwareSeekBar
|
||||
android:id="@+id/seek_bar"
|
||||
style="@style/Widget.AppCompat.SeekBar"
|
||||
android:layout_width="0dp"
|
||||
|
|
33
app/src/main/res/layout/dialog_sponsor_block_api_url.xml
Normal file
33
app/src/main/res/layout/dialog_sponsor_block_api_url.xml
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:clickable="false"
|
||||
android:paddingLeft="@dimen/video_item_search_padding"
|
||||
android:paddingRight="@dimen/video_item_search_padding"
|
||||
android:paddingTop="@dimen/video_item_search_padding">
|
||||
<EditText
|
||||
android:id="@+id/api_url_edit"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_weight="1"
|
||||
android:saveEnabled="true"
|
||||
android:inputType="textUri"
|
||||
android:maxLines="1"
|
||||
android:importantForAutofill="no"
|
||||
android:hint="https://domain.com/api/"/>
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/icon_api_url_help"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:srcCompat="@drawable/ic_help"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:tint="?attr/colorAccent"/>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:clickable="false"
|
||||
android:paddingLeft="@dimen/video_item_search_padding"
|
||||
android:paddingRight="@dimen/video_item_search_padding"
|
||||
android:paddingTop="@dimen/video_item_search_padding">
|
||||
<TextView
|
||||
android:id="@+id/api_url_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:text="@string/sponsor_block_api_url_help_text"/>
|
||||
<Button
|
||||
android:id="@+id/sponsor_block_privacy_policy_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/sponsor_block_privacy_policy_text"/>
|
||||
</LinearLayout>
|
181
app/src/main/res/layout/fragment_sponsor_block.xml
Normal file
181
app/src/main/res/layout/fragment_sponsor_block.xml
Normal file
|
@ -0,0 +1,181 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
tools:context=".fragments.list.sponsorblock.SponsorBlockFragment">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/sponsor_block_controls_mark_segment_start"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
android:layout_weight="1.4"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/sponsor_block_mark_the_start_of_a_segment"
|
||||
android:drawableTop="@drawable/ic_chevron_right"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="@dimen/detail_control_padding"
|
||||
android:text="@string/start"
|
||||
android:textSize="@dimen/detail_control_text_size" />
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="0.4">
|
||||
</Space>
|
||||
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/sponsor_block_controls_mark_segment_end"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
android:layout_weight="1.4"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/sponsor_block_mark_the_end_of_a_segment"
|
||||
android:drawableTop="@drawable/ic_chevron_left"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="@dimen/detail_control_padding"
|
||||
android:text="@string/end"
|
||||
android:textSize="@dimen/detail_control_text_size" />
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="0.4" >
|
||||
</Space>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="5"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center_horizontal">
|
||||
<TextView
|
||||
android:id="@+id/sponsor_block_controls_segment_start"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:textAlignment="center"
|
||||
android:text="00:00:00"
|
||||
tools:ignore="HardcodedText" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:textAlignment="center"
|
||||
android:text="-"
|
||||
tools:ignore="HardcodedText" />
|
||||
<TextView
|
||||
android:id="@+id/sponsor_block_controls_segment_end"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:textAlignment="center"
|
||||
android:text="00:00:00"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="0.4" >
|
||||
</Space>
|
||||
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/sponsor_block_controls_clear_segment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
android:layout_weight="1.4"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/sponsor_block_clear_segment"
|
||||
android:drawableTop="@drawable/ic_delete"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="@dimen/detail_control_padding"
|
||||
android:text="@string/clear"
|
||||
android:textSize="@dimen/detail_control_text_size" />
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="0.4">
|
||||
</Space>
|
||||
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/sponsor_block_controls_submit_segment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/detail_control_height"
|
||||
android:layout_weight="1.4"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/sponsor_block_submit_segment"
|
||||
android:drawableTop="@drawable/ic_upload"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="@dimen/detail_control_padding"
|
||||
android:text="@string/submit"
|
||||
android:textSize="@dimen/detail_control_text_size" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/skipping_is_enabled_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/sponsor_block_skip_marked_segments" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/channel_is_whitelisted_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/sponsor_block_whitelist_channel" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/segment_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/list_segments_item"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
113
app/src/main/res/layout/list_segments_item.xml
Normal file
113
app/src/main/res/layout/list_segments_item.xml
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="horizontal">
|
||||
<!-- segment color -->
|
||||
<RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<!-- skip to highlight -->
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/item_segment_skip_to_highlight"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:contentDescription="@string/description_skip_to_highlight"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_fast_forward" />
|
||||
|
||||
<!-- segment type (color) -->
|
||||
<View
|
||||
android:id="@+id/item_segment_color_view"
|
||||
android:layout_width="10dp"
|
||||
android:layout_height="10dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:background="@color/interaction_segment" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- segment name -->
|
||||
<TextView
|
||||
android:id="@+id/item_segment_category_name_textview"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2"
|
||||
android:layout_gravity="center"
|
||||
android:textAlignment="center"
|
||||
tools:text="Intermission/Intro Animation" />
|
||||
|
||||
<!-- start time, end time -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="4"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center">
|
||||
<TextView
|
||||
android:id="@+id/item_segment_start_time_textview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:textAlignment="center"
|
||||
tools:text="00:00:00" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:textAlignment="center"
|
||||
android:text="-" />
|
||||
<TextView
|
||||
android:id="@+id/item_segment_end_time_textview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:textAlignment="center"
|
||||
tools:text="99:99:99" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- vote up -->
|
||||
<ImageView
|
||||
android:id="@+id/item_segment_vote_up_imageview"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:contentDescription="@string/description_up_vote_segment"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_thumb_up" />
|
||||
|
||||
<!-- vote down -->
|
||||
<ImageView
|
||||
android:id="@+id/item_segment_vote_down_imageview"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:contentDescription="@string/description_down_vote_segment"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_thumb_down" />
|
||||
|
||||
</LinearLayout>
|
6
app/src/main/res/layout/preference_edit_color.xml
Normal file
6
app/src/main/res/layout/preference_edit_color.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/sponsor_block_segment_color_view"
|
||||
android:layout_width="98dp"
|
||||
android:layout_height="6dp"
|
||||
android:background="@color/black"/>
|
|
@ -84,4 +84,16 @@
|
|||
|
||||
<color name="black">#000</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="highlight_segment">#ff1983</color>
|
||||
<color name="self_promo_segment">#ffff00</color>
|
||||
<color name="non_music_segment">#ff9900</color>
|
||||
<color name="preview_segment">#008fd6</color>
|
||||
<color name="filler_segment">#7300ff</color>
|
||||
<color name="pending_segment">#ffffff</color>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -1466,4 +1466,36 @@
|
|||
<item>@string/image_quality_medium_key</item>
|
||||
<item>@string/image_quality_high_key</item>
|
||||
</string-array>
|
||||
|
||||
<!-- SponsorBlock -->
|
||||
<string name="sponsor_block_home_page_key" translatable="false">sponsor_block_home_page</string>
|
||||
<string name="sponsor_block_enable_key" translatable="false">sponsor_block_enable</string>
|
||||
<string name="sponsor_block_api_url_key" translatable="false">sponsor_block_api_url</string>
|
||||
<string name="sponsor_block_notifications_key" translatable="false">sponsor_block_notifications</string>
|
||||
<string name="sponsor_block_privacy_key" translatable="false">sponsor_block_privacy</string>
|
||||
<string name="sponsor_block_categories_key" translatable="false">sponsor_block_categories</string>
|
||||
<string name="sponsor_block_category_reset_key" translatable="false">sponsor_block_category_reset</string>
|
||||
<string name="sponsor_block_category_all_on_key" translatable="false">sponsor_block_category_all_on</string>
|
||||
<string name="sponsor_block_category_all_off_key" translatable="false">sponsor_block_category_all_off</string>
|
||||
<string name="sponsor_block_category_sponsor_key" translatable="false">sponsor_block_category_sponsor</string>
|
||||
<string name="sponsor_block_category_sponsor_color_key" translatable="false">sponsor_block_category_sponsor_color</string>
|
||||
<string name="sponsor_block_category_intro_key" translatable="false">sponsor_block_category_intro</string>
|
||||
<string name="sponsor_block_category_intro_color_key" translatable="false">sponsor_block_category_intro_color</string>
|
||||
<string name="sponsor_block_category_outro_key" translatable="false">sponsor_block_category_outro</string>
|
||||
<string name="sponsor_block_category_outro_color_key" translatable="false">sponsor_block_category_outro_color</string>
|
||||
<string name="sponsor_block_category_interaction_key" translatable="false">sponsor_block_category_interaction</string>
|
||||
<string name="sponsor_block_category_interaction_color_key" translatable="false">sponsor_block_category_interaction_color</string>
|
||||
<string name="sponsor_block_category_highlight_key" translatable="false">sponsor_block_category_highlight</string>
|
||||
<string name="sponsor_block_category_highlight_color_key" translatable="false">sponsor_block_category_highlight_color</string>
|
||||
<string name="sponsor_block_category_self_promo_key" translatable="false">sponsor_block_category_self_promo</string>
|
||||
<string name="sponsor_block_category_self_promo_color_key" translatable="false">sponsor_block_category_self_promo_color</string>
|
||||
<string name="sponsor_block_category_non_music_key" translatable="false">sponsor_block_category_music</string>
|
||||
<string name="sponsor_block_category_non_music_color_key" translatable="false">sponsor_block_category_music_color</string>
|
||||
<string name="sponsor_block_category_preview_key" translatable="false">sponsor_block_category_preview</string>
|
||||
<string name="sponsor_block_category_preview_color_key" translatable="false">sponsor_block_category_preview_color</string>
|
||||
<string name="sponsor_block_category_filler_key" translatable="false">sponsor_block_category_filler</string>
|
||||
<string name="sponsor_block_category_filler_color_key" translatable="false">sponsor_block_category_filler_color</string>
|
||||
<string name="sponsor_block_category_pending_key" translatable="false">sponsor_block_category_pending_key</string>
|
||||
<string name="sponsor_block_category_pending_color_key" translatable="false">sponsor_block_category_pending_color_key</string>
|
||||
<string name="sponsor_block_clear_whitelist_key" translatable="false">sponsor_block_clear_whitelist</string>
|
||||
</resources>
|
||||
|
|
|
@ -273,6 +273,7 @@
|
|||
<string name="comments_tab_description">Comments</string>
|
||||
<string name="related_items_tab_description">Related items</string>
|
||||
<string name="description_tab_description">Description</string>
|
||||
<string name="sponsor_block_tab_description">SponsorBlock</string>
|
||||
<string name="search_no_results">No results</string>
|
||||
<string name="empty_list_subtitle">Nothing here but crickets</string>
|
||||
<string name="import_subscriptions_hint">Import or export subscriptions from the 3-dot menu</string>
|
||||
|
@ -838,4 +839,94 @@
|
|||
<string name="share_playlist_with_list">Share URL list</string>
|
||||
<string name="video_details_list_item">- %1$s: %2$s</string>
|
||||
<string name="share_playlist_content_details">%1$s\n%2$s</string>
|
||||
<!-- SponsorBlock -->
|
||||
<string name="sponsor_block">SponsorBlock</string>
|
||||
<string name="sponsor_block_category_sponsor">Sponsor</string>
|
||||
<string name="sponsor_block_category_intro">Intermission/Intro Animation</string>
|
||||
<string name="sponsor_block_category_outro">Endcards/Credits</string>
|
||||
<string name="sponsor_block_category_interaction">Interaction Reminders (Subscribe)</string>
|
||||
<string name="sponsor_block_category_highlight">Highlight</string>
|
||||
<string name="sponsor_block_category_self_promo">Unpaid/Self Promotion</string>
|
||||
<string name="sponsor_block_category_non_music">Music: Non-Music Section</string>
|
||||
<string name="sponsor_block_category_preview">Preview/Recap</string>
|
||||
<string name="sponsor_block_category_filler">Filler Tangent/Jokes</string>
|
||||
<string name="sponsor_block_category_pending">Pending</string>
|
||||
<string name="sponsor_block_home_page_title">View Website</string>
|
||||
<string name="sponsor_block_home_page_summary">View the official SponsorBlock website.</string>
|
||||
<string name="sponsor_block_enable_title">Enable SponsorBlock</string>
|
||||
<string name="sponsor_block_enable_summary">Use the SponsorBlock API to automatically skip sponsors in videos. This currently only works for YouTube videos.</string>
|
||||
<string name="sponsor_block_api_url_title">API Url</string>
|
||||
<string name="sponsor_block_api_url_summary">The url to use when querying the SponsorBlock API. This must be set for SponsorBlock to work.</string>
|
||||
<string name="sponsor_block_notifications_title">Notify when sponsors are skipped</string>
|
||||
<string name="sponsor_block_notifications_summary">Show a toast notification when a sponsor is automatically skipped.</string>
|
||||
<string name="sponsor_block_privacy_title">View Privacy Policy</string>
|
||||
<string name="sponsor_block_privacy_summary">View SponsorBlock\'s privacy policy.</string>
|
||||
<string name="sponsor_block_api_url_help_text">This is the URL that will be queried when the application needs to know which parts of a video to skip.\n\nYou can set the official URL by clicking the \'Use Official\' option below, though it is highly recommended you view SponsorBlock\'s privacy policy before you do.</string>
|
||||
<string name="sponsor_block_privacy_policy_text">SponsorBlock Privacy Policy</string>
|
||||
<string name="sponsor_block_homepage_url">https://sponsor.ajay.app/</string>
|
||||
<string name="sponsor_block_default_api_url">https://sponsor.ajay.app/api/</string>
|
||||
<string name="sponsor_block_privacy_policy_url">https://gist.github.com/ajayyy/aa9f8ded2b573d4f73a3ffa0ef74f796</string>
|
||||
<string name="sponsor_block_skip_sponsor_toast">Skipped sponsor</string>
|
||||
<string name="sponsor_block_skip_intro_toast">Skipped intermission/intro</string>
|
||||
<string name="sponsor_block_skip_outro_toast">Skipped endcards/credits</string>
|
||||
<string name="sponsor_block_skip_interaction_toast">Skipped interaction reminder</string>
|
||||
<string name="sponsor_block_skip_self_promo_toast">Skipped unpaid/self promo</string>
|
||||
<string name="sponsor_block_skip_non_music_toast">Skipped non-music</string>
|
||||
<string name="sponsor_block_skip_preview_toast">Skipped preview/recap</string>
|
||||
<string name="sponsor_block_skip_filler_toast">Skipped filler</string>
|
||||
<string name="sponsor_block_skip_pending_toast">Skipped pending segment</string>
|
||||
<string name="sponsor_block_toggle_skipping">Toggle skipping sponsors</string>
|
||||
<string name="sponsor_block_clear_whitelist_title">Clear Whitelist</string>
|
||||
<string name="sponsor_block_clear_whitelist_summary">Clear the list of uploaders SponsorBlock will ignore.</string>
|
||||
<string name="sponsor_block_enabled_toast">SponsorBlock enabled</string>
|
||||
<string name="sponsor_block_disabled_toast">SponsorBlock disabled</string>
|
||||
<string name="sponsor_block_whitelist_cleared_toast">Whitelist cleared</string>
|
||||
<string name="sponsor_block_uploader_added_to_whitelist_toast">Channel added to whitelist</string>
|
||||
<string name="sponsor_block_uploader_removed_from_whitelist_toast">Channel removed from whitelist</string>
|
||||
<string name="sponsor_block_confirm_clear_whitelist">Are you sure you want to clear the whitelist?</string>
|
||||
<string name="sponsor_block_confirm_reset_colors">Are you sure you want to reset the category colors?</string>
|
||||
<string name="sponsor_block_segment_voted_up_toast">Up-voted segment</string>
|
||||
<string name="sponsor_block_segment_voted_down_toast">Down-voted segment</string>
|
||||
<string name="sponsor_block_segment_reset_vote_toast">Voting reset for segment</string>
|
||||
<string name="sponsor_block_invalid_start_toast">Invalid start location</string>
|
||||
<string name="sponsor_block_invalid_end_toast">Invalid end location</string>
|
||||
<string name="sponsor_block_marked_start_toast">Marked start of segment</string>
|
||||
<string name="sponsor_block_marked_end_toast">Marked end of segment</string>
|
||||
<string name="sponsor_block_missing_times_toast">Missing start/end time(s)</string>
|
||||
<string name="sponsor_block_select_a_category">Select a category</string>
|
||||
<string name="sponsor_block_clear_marked_segment_prompt">Are you sure you want to clear your new segment?</string>
|
||||
<string name="sponsor_block_upload_success_message">Your segment has been submitted.\n\nIt might take some time for your submission to appear in the video the next time you open it.</string>
|
||||
<string name="sponsor_block_mark_the_start_of_a_segment">Mark the start of a segment</string>
|
||||
<string name="sponsor_block_mark_the_end_of_a_segment">Mark the end of a segment</string>
|
||||
<string name="sponsor_block_clear_segment">Clear segment</string>
|
||||
<string name="sponsor_block_submit_segment">Submit segment</string>
|
||||
<string name="sponsor_block_skip_marked_segments">Skip marked segments</string>
|
||||
<string name="sponsor_block_whitelist_channel">Whitelist channel</string>
|
||||
<string name="settings_category_sponsor_block_title">SponsorBlock (Third-Party Service)</string>
|
||||
<string name="settings_category_sponsor_block_categories_quick_actions">Quick Actions</string>
|
||||
<string name="settings_category_sponsor_block_categories_title">SponsorBlock Categories</string>
|
||||
<string name="settings_category_sponsor_block_categories_summary">Customize which video segments to skip, along with their color markings on the seek bar.</string>
|
||||
<string name="settings_category_sponsor_block_categories_reset_colors_title">Reset Colors</string>
|
||||
<string name="settings_category_sponsor_block_categories_all_colors_on_title">All On</string>
|
||||
<string name="settings_category_sponsor_block_categories_all_colors_off_title">All Off</string>
|
||||
<string name="settings_category_sponsor_block_category_enable_title">Enable</string>
|
||||
<string name="settings_category_sponsor_block_category_color">Seek Bar Color</string>
|
||||
<string name="settings_category_sponsor_block_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_sponsor_block_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_sponsor_block_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_sponsor_block_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_sponsor_block_category_highlight_summary">The part of the video that most people are looking for. Similar to "Video starts at x" comments.</string>
|
||||
<string name="settings_category_sponsor_block_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_sponsor_block_category_non_music_summary">Only for use in music videos. This includes introductions or outros in music videos.</string>
|
||||
<string name="settings_category_sponsor_block_category_preview_summary">Quick recap of previous episodes, or a preview of what\'s coming up later in the current video. Meant for edited together clips, not for spoken summaries.</string>
|
||||
<string name="settings_category_sponsor_block_category_filler_summary">This is for tangential scenes added only for filler or humor that are not required to understand the main content of the video.</string>
|
||||
<string name="settings_category_sponsor_block_category_pending_summary">Represents a new segment ready to be submitted.</string>
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
<string name="submit">Submit</string>
|
||||
<string name="end">End</string>
|
||||
<string name="invalid_color_toast">Invalid color</string>
|
||||
<string name="description_up_vote_segment">Up-vote segment</string>
|
||||
<string name="description_down_vote_segment">Down-vote segment</string>
|
||||
<string name="description_skip_to_highlight">Skip to highlight</string>
|
||||
</resources>
|
||||
|
|
|
@ -47,6 +47,12 @@
|
|||
android:title="@string/settings_category_updates_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.schabi.newpipe.settings.SponsorBlockSettingsFragment"
|
||||
android:icon="@drawable/ic_sponsor_block_enable"
|
||||
android:title="@string/sponsor_block"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.schabi.newpipe.settings.DebugSettingsFragment"
|
||||
android:icon="@drawable/ic_bug_report"
|
||||
|
|
257
app/src/main/res/xml/sponsor_block_category_settings.xml
Normal file
257
app/src/main/res/xml/sponsor_block_category_settings.xml
Normal file
|
@ -0,0 +1,257 @@
|
|||
<?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_sponsor_block_categories_title">
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/settings_category_header_layout"
|
||||
android:title="@string/settings_category_sponsor_block_categories_quick_actions">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:key="@string/sponsor_block_category_all_on_key"
|
||||
android:title="@string/settings_category_sponsor_block_categories_all_colors_on_title"/>
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:key="@string/sponsor_block_category_all_off_key"
|
||||
android:title="@string/settings_category_sponsor_block_categories_all_colors_off_title"/>
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:key="@string/sponsor_block_category_reset_key"
|
||||
android:title="@string/settings_category_sponsor_block_categories_reset_colors_title"/>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/settings_category_header_layout"
|
||||
android:title="@string/sponsor_block_category_sponsor">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:summary="@string/settings_category_sponsor_block_category_sponsor_summary"
|
||||
android:selectable="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:defaultValue="true"
|
||||
android:key="@string/sponsor_block_category_sponsor_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_enable_title"/>
|
||||
|
||||
<org.schabi.newpipe.settings.custom.EditColorPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:dependency="@string/sponsor_block_category_sponsor_key"
|
||||
android:defaultValue="@color/sponsor_segment"
|
||||
android:key="@string/sponsor_block_category_sponsor_color_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_color"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/settings_category_header_layout"
|
||||
android:title="@string/sponsor_block_category_intro">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:summary="@string/settings_category_sponsor_block_category_intro_summary"
|
||||
android:selectable="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:defaultValue="true"
|
||||
android:key="@string/sponsor_block_category_intro_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_enable_title"/>
|
||||
|
||||
<org.schabi.newpipe.settings.custom.EditColorPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:dependency="@string/sponsor_block_category_intro_key"
|
||||
android:defaultValue="@color/intro_segment"
|
||||
android:key="@string/sponsor_block_category_intro_color_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_color"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/settings_category_header_layout"
|
||||
android:title="@string/sponsor_block_category_outro">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:summary="@string/settings_category_sponsor_block_category_outro_summary"
|
||||
android:selectable="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:defaultValue="true"
|
||||
android:key="@string/sponsor_block_category_outro_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_enable_title"/>
|
||||
|
||||
<org.schabi.newpipe.settings.custom.EditColorPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:dependency="@string/sponsor_block_category_outro_key"
|
||||
android:defaultValue="@color/outro_segment"
|
||||
android:key="@string/sponsor_block_category_outro_color_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_color"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/settings_category_header_layout"
|
||||
android:title="@string/sponsor_block_category_interaction">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:summary="@string/settings_category_sponsor_block_category_interaction_summary"
|
||||
android:selectable="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:defaultValue="true"
|
||||
android:key="@string/sponsor_block_category_interaction_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_enable_title"/>
|
||||
|
||||
<org.schabi.newpipe.settings.custom.EditColorPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:dependency="@string/sponsor_block_category_interaction_key"
|
||||
android:defaultValue="@color/interaction_segment"
|
||||
android:key="@string/sponsor_block_category_interaction_color_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_color"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/settings_category_header_layout"
|
||||
android:title="@string/sponsor_block_category_highlight">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:summary="@string/settings_category_sponsor_block_category_highlight_summary"
|
||||
android:selectable="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:defaultValue="true"
|
||||
android:key="@string/sponsor_block_category_highlight_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_enable_title"/>
|
||||
|
||||
<org.schabi.newpipe.settings.custom.EditColorPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:dependency="@string/sponsor_block_category_highlight_key"
|
||||
android:defaultValue="@color/highlight_segment"
|
||||
android:key="@string/sponsor_block_category_highlight_color_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_color"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/settings_category_header_layout"
|
||||
android:title="@string/sponsor_block_category_self_promo">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:summary="@string/settings_category_sponsor_block_category_self_promo_summary"
|
||||
android:selectable="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:defaultValue="true"
|
||||
android:key="@string/sponsor_block_category_self_promo_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_enable_title"/>
|
||||
|
||||
<org.schabi.newpipe.settings.custom.EditColorPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:dependency="@string/sponsor_block_category_self_promo_key"
|
||||
android:defaultValue="@color/self_promo_segment"
|
||||
android:key="@string/sponsor_block_category_self_promo_color_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_color"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/settings_category_header_layout"
|
||||
android:title="@string/sponsor_block_category_non_music">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:summary="@string/settings_category_sponsor_block_category_non_music_summary"
|
||||
android:selectable="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:defaultValue="true"
|
||||
android:key="@string/sponsor_block_category_non_music_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_enable_title"/>
|
||||
|
||||
<org.schabi.newpipe.settings.custom.EditColorPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:dependency="@string/sponsor_block_category_non_music_key"
|
||||
android:defaultValue="@color/non_music_segment"
|
||||
android:key="@string/sponsor_block_category_non_music_color_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_color"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/settings_category_header_layout"
|
||||
android:title="@string/sponsor_block_category_preview">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:summary="@string/settings_category_sponsor_block_category_preview_summary"
|
||||
android:selectable="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:defaultValue="true"
|
||||
android:key="@string/sponsor_block_category_preview_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_enable_title"/>
|
||||
|
||||
<org.schabi.newpipe.settings.custom.EditColorPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:dependency="@string/sponsor_block_category_preview_key"
|
||||
android:defaultValue="@color/preview_segment"
|
||||
android:key="@string/sponsor_block_category_preview_color_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_color"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/settings_category_header_layout"
|
||||
android:title="@string/sponsor_block_category_filler">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:summary="@string/settings_category_sponsor_block_category_filler_summary"
|
||||
android:selectable="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:defaultValue="true"
|
||||
android:key="@string/sponsor_block_category_filler_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_enable_title"/>
|
||||
|
||||
<org.schabi.newpipe.settings.custom.EditColorPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:dependency="@string/sponsor_block_category_filler_key"
|
||||
android:defaultValue="@color/filler_segment"
|
||||
android:key="@string/sponsor_block_category_filler_color_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_color"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/settings_category_header_layout"
|
||||
android:title="@string/sponsor_block_category_pending">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:summary="@string/settings_category_sponsor_block_category_pending_summary"
|
||||
android:selectable="false"/>
|
||||
|
||||
<org.schabi.newpipe.settings.custom.EditColorPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:defaultValue="@color/pending_segment"
|
||||
android:key="@string/sponsor_block_category_pending_color_key"
|
||||
android:title="@string/settings_category_sponsor_block_category_color"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
60
app/src/main/res/xml/sponsor_block_settings.xml
Normal file
60
app/src/main/res/xml/sponsor_block_settings.xml
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?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/sponsor_block">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:key="@string/sponsor_block_home_page_key"
|
||||
android:summary="@string/sponsor_block_home_page_summary"
|
||||
android:title="@string/sponsor_block_home_page_title"/>
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:key="@string/sponsor_block_privacy_key"
|
||||
android:summary="@string/sponsor_block_privacy_summary"
|
||||
android:title="@string/sponsor_block_privacy_title"/>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/settings_category_header_layout"
|
||||
android:title="@string/settings">
|
||||
|
||||
<SwitchPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:defaultValue="true"
|
||||
android:key="@string/sponsor_block_enable_key"
|
||||
android:summary="@string/sponsor_block_enable_summary"
|
||||
android:title="@string/sponsor_block_enable_title"/>
|
||||
|
||||
<org.schabi.newpipe.settings.custom.SponsorBlockApiUrlPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:dependency="@string/sponsor_block_enable_key"
|
||||
android:defaultValue="@string/sponsor_block_default_api_url"
|
||||
android:key="@string/sponsor_block_api_url_key"
|
||||
android:summary="@string/sponsor_block_api_url_summary"
|
||||
android:title="@string/sponsor_block_api_url_title"/>
|
||||
|
||||
<SwitchPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:dependency="@string/sponsor_block_enable_key"
|
||||
android:defaultValue="true"
|
||||
android:key="@string/sponsor_block_notifications_key"
|
||||
android:summary="@string/sponsor_block_notifications_summary"
|
||||
android:title="@string/sponsor_block_notifications_title"/>
|
||||
|
||||
<PreferenceScreen
|
||||
app:iconSpaceReserved="false"
|
||||
android:dependency="@string/sponsor_block_enable_key"
|
||||
android:fragment="org.schabi.newpipe.settings.SponsorBlockCategoriesSettingsFragment"
|
||||
android:key="@string/sponsor_block_categories_key"
|
||||
android:title="@string/settings_category_sponsor_block_categories_title"
|
||||
android:summary="@string/settings_category_sponsor_block_categories_summary"/>
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
android:dependency="@string/sponsor_block_enable_key"
|
||||
android:key="@string/sponsor_block_clear_whitelist_key"
|
||||
android:summary="@string/sponsor_block_clear_whitelist_summary"
|
||||
android:title="@string/sponsor_block_clear_whitelist_title"/>
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
Loading…
Add table
Reference in a new issue