mirror of
https://github.com/MaintainTeam/LastPipeBender.git
synced 2025-03-01 05:48:22 +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,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 7,
|
"version": 7,
|
||||||
"identityHash": "012fc8e7ad3333f1597347f34e76a513",
|
"identityHash": "7dcdec7a500be9088f7a9a4767292b41",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "subscriptions",
|
"tableName": "subscriptions",
|
||||||
|
@ -58,10 +58,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"uid"
|
"uid"
|
||||||
],
|
]
|
||||||
"autoGenerate": true
|
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
|
@ -107,10 +107,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"id"
|
"id"
|
||||||
],
|
]
|
||||||
"autoGenerate": true
|
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
|
@ -209,10 +209,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"uid"
|
"uid"
|
||||||
],
|
]
|
||||||
"autoGenerate": true
|
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
|
@ -252,11 +252,11 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"stream_id",
|
"stream_id",
|
||||||
"access_date"
|
"access_date"
|
||||||
],
|
]
|
||||||
"autoGenerate": false
|
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
|
@ -301,10 +301,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"stream_id"
|
"stream_id"
|
||||||
],
|
]
|
||||||
"autoGenerate": false
|
|
||||||
},
|
},
|
||||||
"indices": [],
|
"indices": [],
|
||||||
"foreignKeys": [
|
"foreignKeys": [
|
||||||
|
@ -351,10 +351,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"uid"
|
"uid"
|
||||||
],
|
]
|
||||||
"autoGenerate": true
|
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
|
@ -393,11 +393,11 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"playlist_id",
|
"playlist_id",
|
||||||
"join_index"
|
"join_index"
|
||||||
],
|
]
|
||||||
"autoGenerate": false
|
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
|
@ -493,10 +493,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"uid"
|
"uid"
|
||||||
],
|
]
|
||||||
"autoGenerate": true
|
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
|
@ -539,11 +539,11 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"stream_id",
|
"stream_id",
|
||||||
"subscription_id"
|
"subscription_id"
|
||||||
],
|
]
|
||||||
"autoGenerate": false
|
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
|
@ -611,10 +611,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"uid"
|
"uid"
|
||||||
],
|
]
|
||||||
"autoGenerate": true
|
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
|
@ -647,11 +647,11 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"group_id",
|
"group_id",
|
||||||
"subscription_id"
|
"subscription_id"
|
||||||
],
|
]
|
||||||
"autoGenerate": false
|
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
|
@ -707,10 +707,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"subscription_id"
|
"subscription_id"
|
||||||
],
|
]
|
||||||
"autoGenerate": false
|
|
||||||
},
|
},
|
||||||
"indices": [],
|
"indices": [],
|
||||||
"foreignKeys": [
|
"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": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"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);
|
inFlight(true);
|
||||||
final LoadingDialog loadingDialog = new LoadingDialog(R.string.loading_metadata_title);
|
final LoadingDialog loadingDialog = new LoadingDialog(R.string.loading_metadata_title);
|
||||||
loadingDialog.show(getParentFragmentManager(), "loadingDialog");
|
loadingDialog.show(getParentFragmentManager(), "loadingDialog");
|
||||||
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
|
disposables.add(ExtractorHelper.getStreamInfo(getContext(), currentServiceId,
|
||||||
|
currentUrl, true)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.compose(this::pleaseWait)
|
.compose(this::pleaseWait)
|
||||||
|
@ -837,7 +838,8 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private void openAddToPlaylistDialog(final int currentServiceId, final String currentUrl) {
|
private void openAddToPlaylistDialog(final int currentServiceId, final String currentUrl) {
|
||||||
inFlight(true);
|
inFlight(true);
|
||||||
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, false)
|
disposables.add(ExtractorHelper.getStreamInfo(getContext(), currentServiceId,
|
||||||
|
currentUrl, false)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.compose(this::pleaseWait)
|
.compose(this::pleaseWait)
|
||||||
|
@ -970,7 +972,8 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
|
|
||||||
switch (choice.linkType) {
|
switch (choice.linkType) {
|
||||||
case STREAM:
|
case STREAM:
|
||||||
single = ExtractorHelper.getStreamInfo(choice.serviceId, choice.url, false);
|
single = ExtractorHelper.getStreamInfo(
|
||||||
|
this, choice.serviceId, choice.url, false);
|
||||||
userAction = UserAction.REQUESTED_STREAM;
|
userAction = UserAction.REQUESTED_STREAM;
|
||||||
break;
|
break;
|
||||||
case CHANNEL:
|
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.PlaylistEntity;
|
||||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
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.StreamDAO;
|
||||||
import org.schabi.newpipe.database.stream.dao.StreamStateDAO;
|
import org.schabi.newpipe.database.stream.dao.StreamStateDAO;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
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,
|
StreamEntity.class, StreamHistoryEntity.class, StreamStateEntity.class,
|
||||||
PlaylistEntity.class, PlaylistStreamEntity.class, PlaylistRemoteEntity.class,
|
PlaylistEntity.class, PlaylistStreamEntity.class, PlaylistRemoteEntity.class,
|
||||||
FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
|
FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
|
||||||
FeedLastUpdatedEntity.class
|
FeedLastUpdatedEntity.class, SponsorBlockWhitelistEntry.class
|
||||||
},
|
},
|
||||||
version = DB_VER_7
|
version = DB_VER_7
|
||||||
)
|
)
|
||||||
|
@ -62,4 +64,6 @@ public abstract class AppDatabase extends RoomDatabase {
|
||||||
public abstract FeedGroupDAO feedGroupDAO();
|
public abstract FeedGroupDAO feedGroupDAO();
|
||||||
|
|
||||||
public abstract SubscriptionDAO subscriptionDAO();
|
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.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
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.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.stream.Stream;
|
import org.schabi.newpipe.extractor.stream.Stream;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
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.EmptyFragment;
|
||||||
import org.schabi.newpipe.fragments.MainFragment;
|
import org.schabi.newpipe.fragments.MainFragment;
|
||||||
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
|
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.fragments.list.videos.RelatedItemsFragment;
|
||||||
import org.schabi.newpipe.ktx.AnimationType;
|
import org.schabi.newpipe.ktx.AnimationType;
|
||||||
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
|
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.Player;
|
||||||
import org.schabi.newpipe.player.PlayerService;
|
import org.schabi.newpipe.player.PlayerService;
|
||||||
import org.schabi.newpipe.player.PlayerType;
|
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.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.PermissionHelper;
|
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.StreamTypeUtil;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
import org.schabi.newpipe.util.external_communication.KoreUtils;
|
import org.schabi.newpipe.util.external_communication.KoreUtils;
|
||||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
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.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -128,6 +136,7 @@ import java.util.function.Consumer;
|
||||||
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.core.Single;
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||||
import io.reactivex.rxjava3.disposables.Disposable;
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
@ -136,7 +145,8 @@ public final class VideoDetailFragment
|
||||||
extends BaseStateFragment<StreamInfo>
|
extends BaseStateFragment<StreamInfo>
|
||||||
implements BackPressable,
|
implements BackPressable,
|
||||||
PlayerServiceExtendedEventListener,
|
PlayerServiceExtendedEventListener,
|
||||||
OnKeyDownListener {
|
OnKeyDownListener,
|
||||||
|
SponsorBlockFragmentListener {
|
||||||
public static final String KEY_SWITCHING_PLAYERS = "switching_players";
|
public static final String KEY_SWITCHING_PLAYERS = "switching_players";
|
||||||
|
|
||||||
private static final float MAX_OVERLAY_ALPHA = 0.9f;
|
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 COMMENTS_TAB_TAG = "COMMENTS";
|
||||||
private static final String RELATED_TAB_TAG = "NEXT VIDEO";
|
private static final String RELATED_TAB_TAG = "NEXT VIDEO";
|
||||||
private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB";
|
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 EMPTY_TAB_TAG = "EMPTY TAB";
|
||||||
|
|
||||||
private static final String PICASSO_VIDEO_DETAILS_TAG = "PICASSO_VIDEO_DETAILS_TAG";
|
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 showComments;
|
||||||
private boolean showRelatedItems;
|
private boolean showRelatedItems;
|
||||||
private boolean showDescription;
|
private boolean showDescription;
|
||||||
|
private boolean showSponsorBlock;
|
||||||
private String selectedTabTag;
|
private String selectedTabTag;
|
||||||
@AttrRes
|
@AttrRes
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -175,7 +187,11 @@ public final class VideoDetailFragment
|
||||||
private int lastAppBarVerticalOffset = Integer.MAX_VALUE; // prevents useless updates
|
private int lastAppBarVerticalOffset = Integer.MAX_VALUE; // prevents useless updates
|
||||||
|
|
||||||
private final SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener =
|
private final SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener =
|
||||||
(sharedPreferences, key) -> {
|
this::onSharedPreferencesChanged;
|
||||||
|
private Disposable workerIsWhitelisted;
|
||||||
|
|
||||||
|
private void onSharedPreferencesChanged(final SharedPreferences sharedPreferences,
|
||||||
|
final String key) {
|
||||||
if (getString(R.string.show_comments_key).equals(key)) {
|
if (getString(R.string.show_comments_key).equals(key)) {
|
||||||
showComments = sharedPreferences.getBoolean(key, true);
|
showComments = sharedPreferences.getBoolean(key, true);
|
||||||
tabSettingsChanged = true;
|
tabSettingsChanged = true;
|
||||||
|
@ -185,8 +201,11 @@ public final class VideoDetailFragment
|
||||||
} else if (getString(R.string.show_description_key).equals(key)) {
|
} else if (getString(R.string.show_description_key).equals(key)) {
|
||||||
showDescription = sharedPreferences.getBoolean(key, true);
|
showDescription = sharedPreferences.getBoolean(key, true);
|
||||||
tabSettingsChanged = true;
|
tabSettingsChanged = true;
|
||||||
|
} else if (getString(R.string.sponsor_block_enable_key).equals(key)) {
|
||||||
|
showSponsorBlock = sharedPreferences.getBoolean(key, false);
|
||||||
|
tabSettingsChanged = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
@State
|
@State
|
||||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||||
|
@ -212,6 +231,8 @@ public final class VideoDetailFragment
|
||||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||||
@Nullable
|
@Nullable
|
||||||
private Disposable positionSubscriber = null;
|
private Disposable positionSubscriber = null;
|
||||||
|
private Disposable submitSegmentSubscriber;
|
||||||
|
|
||||||
|
|
||||||
private BottomSheetBehavior<FrameLayout> bottomSheetBehavior;
|
private BottomSheetBehavior<FrameLayout> bottomSheetBehavior;
|
||||||
private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback;
|
private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback;
|
||||||
|
@ -230,6 +251,7 @@ public final class VideoDetailFragment
|
||||||
private PlayerService playerService;
|
private PlayerService playerService;
|
||||||
private Player player;
|
private Player player;
|
||||||
private final PlayerHolder playerHolder = PlayerHolder.getInstance();
|
private final PlayerHolder playerHolder = PlayerHolder.getInstance();
|
||||||
|
private SponsorBlockDataManager sponsorBlockDataManager;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Service management
|
// Service management
|
||||||
|
@ -309,6 +331,7 @@ public final class VideoDetailFragment
|
||||||
showComments = prefs.getBoolean(getString(R.string.show_comments_key), true);
|
showComments = prefs.getBoolean(getString(R.string.show_comments_key), true);
|
||||||
showRelatedItems = prefs.getBoolean(getString(R.string.show_next_video_key), true);
|
showRelatedItems = prefs.getBoolean(getString(R.string.show_next_video_key), true);
|
||||||
showDescription = prefs.getBoolean(getString(R.string.show_description_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(
|
selectedTabTag = prefs.getString(
|
||||||
getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG);
|
getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG);
|
||||||
prefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
|
prefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
|
||||||
|
@ -326,6 +349,8 @@ public final class VideoDetailFragment
|
||||||
activity.getContentResolver().registerContentObserver(
|
activity.getContentResolver().registerContentObserver(
|
||||||
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false,
|
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false,
|
||||||
settingsContentObserver);
|
settingsContentObserver);
|
||||||
|
|
||||||
|
sponsorBlockDataManager = new SponsorBlockDataManager(requireContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -426,6 +451,17 @@ public final class VideoDetailFragment
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetach() {
|
||||||
|
super.onDetach();
|
||||||
|
if (submitSegmentSubscriber != null) {
|
||||||
|
submitSegmentSubscriber.dispose();
|
||||||
|
}
|
||||||
|
if (workerIsWhitelisted != null) {
|
||||||
|
workerIsWhitelisted.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
|
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
@ -838,7 +874,7 @@ public final class VideoDetailFragment
|
||||||
|
|
||||||
private void runWorker(final boolean forceLoad, final boolean addToBackStack) {
|
private void runWorker(final boolean forceLoad, final boolean addToBackStack) {
|
||||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||||
currentWorker = ExtractorHelper.getStreamInfo(serviceId, url, forceLoad)
|
currentWorker = ExtractorHelper.getStreamInfo(getContext(), serviceId, url, forceLoad)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(result -> {
|
.subscribe(result -> {
|
||||||
|
@ -901,6 +937,13 @@ public final class VideoDetailFragment
|
||||||
tabContentDescriptions.add(R.string.description_tab_description);
|
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) {
|
if (pageAdapter.getCount() == 0) {
|
||||||
pageAdapter.addFragment(EmptyFragment.newInstance(true), EMPTY_TAB_TAG);
|
pageAdapter.addFragment(EmptyFragment.newInstance(true), EMPTY_TAB_TAG);
|
||||||
}
|
}
|
||||||
|
@ -949,6 +992,13 @@ public final class VideoDetailFragment
|
||||||
pageAdapter.updateItem(DESCRIPTION_TAB_TAG, new DescriptionFragment(info));
|
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);
|
binding.viewPager.setVisibility(View.VISIBLE);
|
||||||
// make sure the tab layout is visible
|
// make sure the tab layout is visible
|
||||||
updateTabLayoutVisibility();
|
updateTabLayoutVisibility();
|
||||||
|
@ -1243,6 +1293,9 @@ public final class VideoDetailFragment
|
||||||
playerUi.removeViewFromParent();
|
playerUi.removeViewFromParent();
|
||||||
binding.playerPlaceholder.addView(playerUi.getBinding().getRoot());
|
binding.playerPlaceholder.addView(playerUi.getBinding().getRoot());
|
||||||
playerUi.setupVideoSurfaceIfNeeded();
|
playerUi.setupVideoSurfaceIfNeeded();
|
||||||
|
if (currentInfo != null) {
|
||||||
|
playerUi.onMarkSeekbarRequested(currentInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1786,6 +1839,9 @@ public final class VideoDetailFragment
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSponsorBlockFragment().ifPresent(
|
||||||
|
fragment -> fragment.setCurrentProgress(currentProgress));
|
||||||
|
|
||||||
if (player.getPlayQueue().getItem().getUrl().equals(url)) {
|
if (player.getPlayQueue().getItem().getUrl().equals(url)) {
|
||||||
updatePlaybackProgress(currentProgress, duration);
|
updatePlaybackProgress(currentProgress, duration);
|
||||||
}
|
}
|
||||||
|
@ -1793,6 +1849,25 @@ public final class VideoDetailFragment
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMetadataUpdate(final StreamInfo info, final PlayQueue queue) {
|
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);
|
final StackItem item = findQueueInStack(queue);
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
// When PlayQueue can have multiple streams (PlaylistPlayQueue or ChannelPlayQueue)
|
// When PlayQueue can have multiple streams (PlaylistPlayQueue or ChannelPlayQueue)
|
||||||
|
@ -2451,4 +2526,147 @@ public final class VideoDetailFragment
|
||||||
lastStableBottomSheetState = newState;
|
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;
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
public class HistoryRecordManager {
|
public class HistoryRecordManager {
|
||||||
|
private final Context context;
|
||||||
private final AppDatabase database;
|
private final AppDatabase database;
|
||||||
private final StreamDAO streamTable;
|
private final StreamDAO streamTable;
|
||||||
private final StreamHistoryDAO streamHistoryTable;
|
private final StreamHistoryDAO streamHistoryTable;
|
||||||
|
@ -69,6 +70,7 @@ public class HistoryRecordManager {
|
||||||
private final String streamHistoryKey;
|
private final String streamHistoryKey;
|
||||||
|
|
||||||
public HistoryRecordManager(final Context context) {
|
public HistoryRecordManager(final Context context) {
|
||||||
|
this.context = context;
|
||||||
database = NewPipeDatabase.getInstance(context);
|
database = NewPipeDatabase.getInstance(context);
|
||||||
streamTable = database.streamDAO();
|
streamTable = database.streamDAO();
|
||||||
streamHistoryTable = database.streamHistoryDAO();
|
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
|
// Duration will not exist if the item was loaded with fast mode, so fetch it if empty
|
||||||
if (info.getDuration() < 0) {
|
if (info.getDuration() < 0) {
|
||||||
final StreamInfo completeInfo = ExtractorHelper.getStreamInfo(
|
final StreamInfo completeInfo = ExtractorHelper.getStreamInfo(
|
||||||
|
context,
|
||||||
info.getServiceId(),
|
info.getServiceId(),
|
||||||
info.getUrl(),
|
info.getUrl(),
|
||||||
false
|
false
|
||||||
|
@ -235,7 +238,7 @@ public class HistoryRecordManager {
|
||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
|
|
||||||
public Maybe<StreamStateEntity> loadStreamState(final PlayQueueItem queueItem) {
|
public Maybe<StreamStateEntity> loadStreamState(final PlayQueueItem queueItem) {
|
||||||
return queueItem.getStream()
|
return queueItem.getStream(context)
|
||||||
.map(info -> streamTable.upsert(new StreamEntity(info)))
|
.map(info -> streamTable.upsert(new StreamEntity(info)))
|
||||||
.flatMapPublisher(streamStateTable::getState)
|
.flatMapPublisher(streamStateTable::getState)
|
||||||
.firstElement()
|
.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.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.PermissionHelper;
|
import org.schabi.newpipe.util.PermissionHelper;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
import org.schabi.newpipe.util.SponsorBlockHelper;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -229,9 +230,12 @@ public final class PlayQueueActivity extends AppCompatActivity
|
||||||
} else {
|
} else {
|
||||||
onQueueUpdate(player.getPlayQueue());
|
onQueueUpdate(player.getPlayQueue());
|
||||||
buildComponents();
|
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.media.AudioManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
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.PlaybackException;
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player.PositionInfo;
|
import com.google.android.exoplayer2.Player.PositionInfo;
|
||||||
|
import com.google.android.exoplayer2.SeekParameters;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.Tracks;
|
import com.google.android.exoplayer2.Tracks;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
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.ErrorInfo;
|
||||||
import org.schabi.newpipe.error.ErrorUtil;
|
import org.schabi.newpipe.error.ErrorUtil;
|
||||||
import org.schabi.newpipe.error.UserAction;
|
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.stream.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.Image;
|
import org.schabi.newpipe.extractor.Image;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
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.DependentPreferenceHelper;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
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.image.PicassoHelper;
|
||||||
import org.schabi.newpipe.util.SerializedCache;
|
import org.schabi.newpipe.util.SerializedCache;
|
||||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||||
|
@ -262,7 +268,9 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
private final SharedPreferences prefs;
|
private final SharedPreferences prefs;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final HistoryRecordManager recordManager;
|
private final HistoryRecordManager recordManager;
|
||||||
|
private SponsorBlockMode sponsorBlockMode = SponsorBlockMode.DISABLED;
|
||||||
|
|
||||||
|
private final SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor
|
// Constructor
|
||||||
|
@ -273,6 +281,25 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
context = service;
|
context = service;
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
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);
|
recordManager = new HistoryRecordManager(context);
|
||||||
|
|
||||||
setupBroadcastReceiver();
|
setupBroadcastReceiver();
|
||||||
|
@ -637,7 +664,7 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playQueue != null) {
|
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() {
|
public void triggerProgressUpdate() {
|
||||||
|
triggerProgressUpdate(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void triggerProgressUpdate(final boolean isRewind) {
|
||||||
if (exoPlayerIsNull()) {
|
if (exoPlayerIsNull()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdateProgress(Math.max((int) simpleExoPlayer.getCurrentPosition(), 0),
|
final int currentProgress = Math.max((int) simpleExoPlayer.getCurrentPosition(), 0);
|
||||||
(int) simpleExoPlayer.getDuration(), simpleExoPlayer.getBufferedPercentage());
|
|
||||||
|
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() {
|
private Disposable getProgressUpdateDisposable() {
|
||||||
|
@ -1706,7 +1782,7 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
|
|
||||||
public void fastForward() {
|
public void fastForward() {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "fastRewind() called");
|
Log.d(TAG, "fastForward() called");
|
||||||
}
|
}
|
||||||
seekBy(retrieveSeekDurationFromPreferences(this));
|
seekBy(retrieveSeekDurationFromPreferences(this));
|
||||||
triggerProgressUpdate();
|
triggerProgressUpdate();
|
||||||
|
@ -1717,7 +1793,7 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
Log.d(TAG, "fastRewind() called");
|
Log.d(TAG, "fastRewind() called");
|
||||||
}
|
}
|
||||||
seekBy(-retrieveSeekDurationFromPreferences(this));
|
seekBy(-retrieveSeekDurationFromPreferences(this));
|
||||||
triggerProgressUpdate();
|
triggerProgressUpdate(true);
|
||||||
}
|
}
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
@ -2302,6 +2378,41 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
return Optional.ofNullable(fragmentListener);
|
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
|
* @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;
|
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.os.Handler;
|
||||||
import android.util.Log;
|
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.schedulers.Schedulers;
|
||||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
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 {
|
public class MediaSourceManager {
|
||||||
@NonNull
|
@NonNull
|
||||||
private final String TAG = "MediaSourceManager@" + hashCode();
|
private final String TAG = "MediaSourceManager@" + hashCode();
|
||||||
|
@ -69,6 +70,8 @@ public class MediaSourceManager {
|
||||||
*/
|
*/
|
||||||
private static final int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1;
|
private static final int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Context context;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final PlaybackListener playbackListener;
|
private final PlaybackListener playbackListener;
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -125,14 +128,16 @@ public class MediaSourceManager {
|
||||||
|
|
||||||
private final Handler removeMediaSourceHandler = new Handler();
|
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) {
|
@NonNull final PlayQueue playQueue) {
|
||||||
this(listener, playQueue, 400L,
|
this(context, listener, playQueue, 400L,
|
||||||
/*playbackNearEndGapMillis=*/TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS),
|
/*playbackNearEndGapMillis=*/TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS),
|
||||||
/*progressUpdateIntervalMillis*/TimeUnit.MILLISECONDS.convert(2, 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,
|
@NonNull final PlayQueue playQueue,
|
||||||
final long loadDebounceMillis,
|
final long loadDebounceMillis,
|
||||||
final long playbackNearEndGapMillis,
|
final long playbackNearEndGapMillis,
|
||||||
|
@ -146,6 +151,7 @@ public class MediaSourceManager {
|
||||||
+ " ms] for them to be useful.");
|
+ " ms] for them to be useful.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.context = context;
|
||||||
this.playbackListener = listener;
|
this.playbackListener = listener;
|
||||||
this.playQueue = playQueue;
|
this.playQueue = playQueue;
|
||||||
|
|
||||||
|
@ -420,7 +426,7 @@ public class MediaSourceManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Single<ManagedMediaSource> getLoadedMediaSource(@NonNull final PlayQueueItem stream) {
|
private Single<ManagedMediaSource> getLoadedMediaSource(@NonNull final PlayQueueItem stream) {
|
||||||
return stream.getStream()
|
return stream.getStream(context)
|
||||||
.map(streamInfo -> Optional
|
.map(streamInfo -> Optional
|
||||||
.ofNullable(playbackListener.sourceOf(stream, streamInfo))
|
.ofNullable(playbackListener.sourceOf(stream, streamInfo))
|
||||||
.<ManagedMediaSource>flatMap(source ->
|
.<ManagedMediaSource>flatMap(source ->
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.schabi.newpipe.player.playqueue;
|
package org.schabi.newpipe.player.playqueue;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
@ -122,8 +124,8 @@ public class PlayQueueItem implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public Single<StreamInfo> getStream() {
|
public Single<StreamInfo> getStream(final Context context) {
|
||||||
return ExtractorHelper.getStreamInfo(this.serviceId, this.url, false)
|
return ExtractorHelper.getStreamInfo(context, this.serviceId, this.url, false)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.doOnError(throwable -> error = throwable);
|
.doOnError(throwable -> error = throwable);
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,6 +127,9 @@ public abstract class PlayerUi {
|
||||||
public void onPrepared() {
|
public void onPrepared() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onMarkSeekbarRequested(@NonNull final StreamInfo streamInfo) {
|
||||||
|
}
|
||||||
|
|
||||||
public void onBlocked() {
|
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.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
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.KoreUtils;
|
||||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||||
import org.schabi.newpipe.views.player.PlayerFastSeekOverlay;
|
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()));
|
binding.playbackSpeed.setText(formatSpeed(player.getPlaybackSpeed()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMarkSeekbarRequested(@NonNull final StreamInfo streamInfo) {
|
||||||
|
SponsorBlockHelper.markSegments(context, binding.playbackSeekBar, streamInfo);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBlocked() {
|
public void onBlocked() {
|
||||||
super.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.player_notification_settings, true);
|
||||||
PreferenceManager.setDefaultValues(context, R.xml.update_settings, true);
|
PreferenceManager.setDefaultValues(context, R.xml.update_settings, true);
|
||||||
PreferenceManager.setDefaultValues(context, R.xml.debug_settings, true);
|
PreferenceManager.setDefaultValues(context, R.xml.debug_settings, true);
|
||||||
|
PreferenceManager.setDefaultValues(context, R.xml.sponsor_block_settings, true);
|
||||||
|
PreferenceManager.setDefaultValues(context, R.xml.sponsor_block_category_settings, true);
|
||||||
|
|
||||||
saveDefaultVideoDownloadDirectory(context);
|
saveDefaultVideoDownloadDirectory(context);
|
||||||
saveDefaultAudioDownloadDirectory(context);
|
saveDefaultAudioDownloadDirectory(context);
|
||||||
|
|
|
@ -41,6 +41,8 @@ public final class SettingsResourceRegistry {
|
||||||
add(UpdateSettingsFragment.class, R.xml.update_settings);
|
add(UpdateSettingsFragment.class, R.xml.update_settings);
|
||||||
add(VideoAudioSettingsFragment.class, R.xml.video_audio_settings);
|
add(VideoAudioSettingsFragment.class, R.xml.video_audio_settings);
|
||||||
add(ExoPlayerSettingsFragment.class, R.xml.exoplayer_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(
|
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 static org.schabi.newpipe.util.text.TextLinkifier.SET_LINK_MOVEMENT_METHOD;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
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.linkhandler.ListLinkHandler;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
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.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
|
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) {
|
final boolean forceLoad) {
|
||||||
checkServiceId(serviceId);
|
checkServiceId(serviceId);
|
||||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.STREAM,
|
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,
|
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();
|
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,
|
@NonNull final String url,
|
||||||
final Consumer<StreamInfo> callback) {
|
final Consumer<StreamInfo> callback) {
|
||||||
Toast.makeText(context, R.string.loading_stream_details, Toast.LENGTH_SHORT).show();
|
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())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(result -> {
|
.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 =
|
final Single<StreamInfo> single =
|
||||||
ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false);
|
ExtractorHelper.getStreamInfo(context, service.getServiceId(), cleanUrl, false);
|
||||||
disposables.add(single.subscribeOn(Schedulers.io())
|
disposables.add(single.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(info -> {
|
.subscribe(info -> {
|
||||||
|
|
|
@ -24,8 +24,6 @@ import android.view.KeyEvent;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.widget.SeekBar;
|
import android.widget.SeekBar;
|
||||||
|
|
||||||
import androidx.appcompat.widget.AppCompatSeekBar;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.util.DeviceUtils;
|
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
|
* (onStartTrackingTouch/onStopTrackingTouch), so existing code does not need to be changed to
|
||||||
* work with it.
|
* work with it.
|
||||||
*/
|
*/
|
||||||
public final class FocusAwareSeekBar extends AppCompatSeekBar {
|
public final class FocusAwareSeekBar extends MarkableSeekBar {
|
||||||
private NestedListener listener;
|
private NestedListener listener;
|
||||||
|
|
||||||
private ViewTreeObserver treeObserver;
|
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:ignore="HardcodedText"
|
||||||
tools:text="1:06:29" />
|
tools:text="1:06:29" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatSeekBar
|
<org.schabi.newpipe.views.FocusAwareSeekBar
|
||||||
android:id="@+id/seek_bar"
|
android:id="@+id/seek_bar"
|
||||||
style="@style/Widget.AppCompat.SeekBar"
|
style="@style/Widget.AppCompat.SeekBar"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
|
@ -116,7 +116,7 @@
|
||||||
tools:text="1:06:29" />
|
tools:text="1:06:29" />
|
||||||
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatSeekBar
|
<org.schabi.newpipe.views.FocusAwareSeekBar
|
||||||
android:id="@+id/seek_bar"
|
android:id="@+id/seek_bar"
|
||||||
style="@style/Widget.AppCompat.SeekBar"
|
style="@style/Widget.AppCompat.SeekBar"
|
||||||
android:layout_width="0dp"
|
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>
|
<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>
|
</resources>
|
||||||
|
|
|
@ -1466,4 +1466,36 @@
|
||||||
<item>@string/image_quality_medium_key</item>
|
<item>@string/image_quality_medium_key</item>
|
||||||
<item>@string/image_quality_high_key</item>
|
<item>@string/image_quality_high_key</item>
|
||||||
</string-array>
|
</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>
|
</resources>
|
||||||
|
|
|
@ -273,6 +273,7 @@
|
||||||
<string name="comments_tab_description">Comments</string>
|
<string name="comments_tab_description">Comments</string>
|
||||||
<string name="related_items_tab_description">Related items</string>
|
<string name="related_items_tab_description">Related items</string>
|
||||||
<string name="description_tab_description">Description</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="search_no_results">No results</string>
|
||||||
<string name="empty_list_subtitle">Nothing here but crickets</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>
|
<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="share_playlist_with_list">Share URL list</string>
|
||||||
<string name="video_details_list_item">- %1$s: %2$s</string>
|
<string name="video_details_list_item">- %1$s: %2$s</string>
|
||||||
<string name="share_playlist_content_details">%1$s\n%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>
|
</resources>
|
||||||
|
|
|
@ -47,6 +47,12 @@
|
||||||
android:title="@string/settings_category_updates_title"
|
android:title="@string/settings_category_updates_title"
|
||||||
app:iconSpaceReserved="false" />
|
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
|
<PreferenceScreen
|
||||||
android:fragment="org.schabi.newpipe.settings.DebugSettingsFragment"
|
android:fragment="org.schabi.newpipe.settings.DebugSettingsFragment"
|
||||||
android:icon="@drawable/ic_bug_report"
|
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