diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index e15abd9a0..a0b92a60d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -101,7 +101,6 @@ import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.SponsorBlockUtils; import org.schabi.newpipe.util.ThemeHelper; -import org.schabi.newpipe.util.VideoSegment; import java.util.ArrayList; import java.util.Iterator; @@ -160,8 +159,12 @@ public final class VideoDetailFragment private boolean showRelatedStreams; private boolean showDescription; private String selectedTabTag; - @AttrRes @NonNull final List tabIcons = new ArrayList<>(); - @StringRes @NonNull final List tabContentDescriptions = new ArrayList<>(); + @AttrRes + @NonNull + final List tabIcons = new ArrayList<>(); + @StringRes + @NonNull + final List tabContentDescriptions = new ArrayList<>(); private boolean tabSettingsChanged = false; private int lastAppBarVerticalOffset = Integer.MAX_VALUE; // prevents useless updates @@ -187,12 +190,13 @@ public final class VideoDetailFragment private final CompositeDisposable disposables = new CompositeDisposable(); @Nullable private Disposable positionSubscriber = null; + @Nullable + private Disposable videoSegmentsSubscriber = null; private List sortedVideoStreams; private int selectedVideoStreamIndex = -1; private BottomSheetBehavior bottomSheetBehavior; private BroadcastReceiver broadcastReceiver; - private VideoSegment[] videoSegments; /*////////////////////////////////////////////////////////////////////////// // Views @@ -379,6 +383,9 @@ public final class VideoDetailFragment if (positionSubscriber != null) { positionSubscriber.dispose(); } + if (videoSegmentsSubscriber != null) { + videoSegmentsSubscriber.dispose(); + } if (currentWorker != null) { currentWorker.dispose(); } @@ -678,8 +685,8 @@ public final class VideoDetailFragment if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { animate(binding.touchAppendDetail, true, 250, AnimationType.ALPHA, 0, () -> - animate(binding.touchAppendDetail, false, 1500, - AnimationType.ALPHA, 1000)); + animate(binding.touchAppendDetail, false, 1500, + AnimationType.ALPHA, 1000)); } return false; }; @@ -887,43 +894,7 @@ public final class VideoDetailFragment private void runWorker(final boolean forceLoad, final boolean addToBackStack) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); - - final String apiUrl = prefs.getString(getContext() - .getString(R.string.sponsor_block_api_url_key), null); - final boolean isSponsorBlockEnabled = prefs.getBoolean(getContext() - .getString(R.string.sponsor_block_enable_key), false); - final boolean includeSponsorCategory = prefs.getBoolean(getContext() - .getString(R.string.sponsor_block_category_sponsor_key), false); - final boolean includeIntroCategory = prefs.getBoolean(getContext() - .getString(R.string.sponsor_block_category_intro_key), false); - final boolean includeOutroCategory = prefs.getBoolean(getContext() - .getString(R.string.sponsor_block_category_outro_key), false); - final boolean includeInteractionCategory = prefs.getBoolean(getContext() - .getString(R.string.sponsor_block_category_interaction_key), false); - final boolean includeSelfPromoCategory = prefs.getBoolean(getContext() - .getString(R.string.sponsor_block_category_self_promo_key), false); - final boolean includeMusicCategory = prefs.getBoolean(getContext() - .getString(R.string.sponsor_block_category_non_music_key), false); - currentWorker = ExtractorHelper.getStreamInfo(serviceId, url, forceLoad) - .flatMap(streamInfo -> Single.fromCallable(() -> { - if (isSponsorBlockEnabled - && streamInfo.getUrl().startsWith("https://www.youtube.com") - && apiUrl != null - && !apiUrl.isEmpty()) { - this.videoSegments = SponsorBlockUtils.getYouTubeVideoSegments( - apiUrl, - streamInfo.getId(), - includeSponsorCategory, - includeIntroCategory, - includeOutroCategory, - includeInteractionCategory, - includeSelfPromoCategory, - includeMusicCategory); - } - - return streamInfo; - })) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { @@ -1182,7 +1153,6 @@ public final class VideoDetailFragment playerService.getView().setVisibility(View.GONE); } addVideoPlayerView(); - playerService.setVideoSegments(videoSegments); final Intent playerIntent = NavigationHelper .getPlayerIntent(requireContext(), MainPlayer.class, queue, true, autoPlayEnabled); @@ -1634,29 +1604,43 @@ public final class VideoDetailFragment } public void openDownloadDialog() { - try { - final DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo); - downloadDialog.setVideoStreams(sortedVideoStreams); - downloadDialog.setAudioStreams(currentInfo.getAudioStreams()); - downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex); - downloadDialog.setSubtitleStreams(currentInfo.getSubtitles()); - downloadDialog.setVideoSegments(videoSegments); + videoSegmentsSubscriber = Single.fromCallable(() -> { + try { + return SponsorBlockUtils.getYouTubeVideoSegments(getContext(), currentInfo); + } catch (final Exception e) { + // TODO: handle + return null; + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(videoSegments -> { + try { + final DownloadDialog downloadDialog = + DownloadDialog.newInstance(currentInfo); + downloadDialog.setVideoStreams(sortedVideoStreams); + downloadDialog.setAudioStreams(currentInfo.getAudioStreams()); + downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex); + downloadDialog.setSubtitleStreams(currentInfo.getSubtitles()); + downloadDialog.setVideoSegments(videoSegments); - downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog"); - } catch (final Exception e) { - final ErrorInfo info = ErrorInfo.make(UserAction.UI_ERROR, - ServiceList.all() - .get(currentInfo - .getServiceId()) - .getServiceInfo() - .getName(), "", - R.string.could_not_setup_download_menu); + downloadDialog.show( + activity.getSupportFragmentManager(), "downloadDialog"); + } catch (final Exception e) { + final ErrorInfo info = ErrorInfo.make(UserAction.UI_ERROR, + ServiceList.all() + .get(currentInfo + .getServiceId()) + .getServiceInfo() + .getName(), "", + R.string.could_not_setup_download_menu); - ErrorActivity.reportError(activity, - e, - activity.getClass(), - activity.findViewById(android.R.id.content), info); - } + ErrorActivity.reportError(activity, + e, + activity.getClass(), + activity.findViewById(android.R.id.content), info); + } + }); } /*////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java index 0813bf86b..945bc9a04 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java @@ -37,7 +37,6 @@ import androidx.core.content.ContextCompat; import org.schabi.newpipe.App; import org.schabi.newpipe.databinding.PlayerBinding; import org.schabi.newpipe.util.ThemeHelper; -import org.schabi.newpipe.util.VideoSegment; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; @@ -244,10 +243,6 @@ public final class MainPlayer extends Service { } } - public void setVideoSegments(final VideoSegment[] videoSegments) { - player.setVideoSegments(videoSegments); - } - public class LocalBinder extends Binder { diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 0f3d78f5e..f896bef8c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -227,6 +227,7 @@ public final class Player implements public static final String SELECT_ON_APPEND = "select_on_append"; public static final String PLAYER_TYPE = "player_type"; public static final String IS_MUTED = "is_muted"; + public static final String VIDEO_SEGMENTS = "video_segments"; /*////////////////////////////////////////////////////////////////////////// // Time constants @@ -381,7 +382,6 @@ public final class Player implements /*////////////////////////////////////////////////////////////////////////// // SponsorBlock //////////////////////////////////////////////////////////////////////////*/ - private VideoSegment[] videoSegments; private SponsorBlockMode sponsorBlockMode = SponsorBlockMode.DISABLED; @@ -842,7 +842,7 @@ public final class Player implements } if (playQueue != null) { - playQueueManager = new MediaSourceManager(this, playQueue); + playQueueManager = new MediaSourceManager(context, this, playQueue); } } @@ -4223,13 +4223,6 @@ public final class Player implements // SponsorBlock //////////////////////////////////////////////////////////////////////////*/ //region - public VideoSegment[] getVideoSegments() { - return videoSegments; - } - - public void setVideoSegments(final VideoSegment[] videoSegments) { - this.videoSegments = videoSegments; - } public void onBlockingSponsorsButtonClicked() { if (DEBUG) { @@ -4263,6 +4256,7 @@ public final class Player implements } public VideoSegment getSkippableSegment(final int progress) { + final VideoSegment[] videoSegments = currentItem.getVideoSegments(); if (videoSegments == null) { return null; } @@ -4285,7 +4279,7 @@ public final class Player implements private void markSegments() { binding.playbackSeekBar.clearMarkers(); - final VideoSegment[] segments = getVideoSegments(); + final VideoSegment[] segments = currentItem.getVideoSegments(); if (segments == null || segments.length == 0) { return; diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index f3049d11d..9d1543c46 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.player.playback; +import android.content.Context; import android.os.Handler; import android.util.Log; @@ -23,6 +24,7 @@ import org.schabi.newpipe.player.playqueue.events.PlayQueueEvent; import org.schabi.newpipe.player.playqueue.events.RemoveEvent; import org.schabi.newpipe.player.playqueue.events.ReorderEvent; import org.schabi.newpipe.util.ServiceHelper; +import org.schabi.newpipe.util.SponsorBlockUtils; import java.util.Collection; import java.util.Collections; @@ -69,6 +71,8 @@ public class MediaSourceManager { */ private static final int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1; + @NonNull + private final Context context; @NonNull private final PlaybackListener playbackListener; @NonNull @@ -125,14 +129,16 @@ public class MediaSourceManager { private final Handler removeMediaSourceHandler = new Handler(); - public MediaSourceManager(@NonNull final PlaybackListener listener, + public MediaSourceManager(@NonNull final Context context, + @NonNull final PlaybackListener listener, @NonNull final PlayQueue playQueue) { - this(listener, playQueue, 400L, + this(context, listener, playQueue, 400L, /*playbackNearEndGapMillis=*/TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS), /*progressUpdateIntervalMillis*/TimeUnit.MILLISECONDS.convert(2, TimeUnit.SECONDS)); } - private MediaSourceManager(@NonNull final PlaybackListener listener, + private MediaSourceManager(@NonNull final Context context, + @NonNull final PlaybackListener listener, @NonNull final PlayQueue playQueue, final long loadDebounceMillis, final long playbackNearEndGapMillis, @@ -146,6 +152,7 @@ public class MediaSourceManager { + " ms] for them to be useful."); } + this.context = context; this.playbackListener = listener; this.playQueue = playQueue; @@ -428,6 +435,9 @@ public class MediaSourceManager { final long expiration = System.currentTimeMillis() + ServiceHelper.getCacheExpirationMillis(streamInfo.getServiceId()); + + stream.setVideoSegments(SponsorBlockUtils.getYouTubeVideoSegments(context, streamInfo)); + return new LoadedMediaSource(source, stream, expiration); }).onErrorReturn(throwable -> new FailedMediaSource(stream, new StreamInfoLoadException(throwable))); diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.java index 1c38ebbca..2e0891e5c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.java @@ -7,6 +7,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.util.ExtractorHelper; +import org.schabi.newpipe.util.VideoSegment; import java.io.Serializable; @@ -35,6 +36,8 @@ public class PlayQueueItem implements Serializable { private long recoveryPosition; private Throwable error; + private VideoSegment[] videoSegments; + PlayQueueItem(@NonNull final StreamInfo info) { this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(), info.getThumbnailUrl(), info.getUploaderName(), info.getStreamType()); @@ -142,4 +145,12 @@ public class PlayQueueItem implements Serializable { public void setAutoQueued(final boolean autoQueued) { isAutoQueued = autoQueued; } + + public VideoSegment[] getVideoSegments() { + return videoSegments; + } + + public void setVideoSegments(final VideoSegment[] videoSegments) { + this.videoSegments = videoSegments; + } } diff --git a/app/src/main/java/org/schabi/newpipe/util/SponsorBlockUtils.java b/app/src/main/java/org/schabi/newpipe/util/SponsorBlockUtils.java index 4ee901e59..530f1e78a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/SponsorBlockUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/SponsorBlockUtils.java @@ -2,10 +2,13 @@ package org.schabi.newpipe.util; import android.app.Application; import android.content.Context; +import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.text.TextUtils; import android.util.Log; +import androidx.preference.PreferenceManager; + import com.grack.nanojson.JsonArray; import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; @@ -13,6 +16,8 @@ import com.grack.nanojson.JsonParser; import org.schabi.newpipe.App; import org.schabi.newpipe.DownloaderImpl; import org.schabi.newpipe.MainActivity; +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.stream.StreamInfo; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; @@ -29,15 +34,40 @@ public final class SponsorBlockUtils { } @SuppressWarnings("CheckStyle") - public static VideoSegment[] getYouTubeVideoSegments(final String apiUrl, - final String videoId, - final boolean includeSponsorCategory, - final boolean includeIntroCategory, - final boolean includeOutroCategory, - final boolean includeInteractionCategory, - final boolean includeSelfPromoCategory, - final boolean includeMusicCategory) + public static VideoSegment[] getYouTubeVideoSegments(final Context context, + final StreamInfo streamInfo) throws UnsupportedEncodingException { + 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 String apiUrl = prefs.getString(context + .getString(R.string.sponsor_block_api_url_key), null); + + if (!streamInfo.getUrl().startsWith("https://www.youtube.com") + || apiUrl == null + || apiUrl.isEmpty()) { + return null; + } + + final boolean includeSponsorCategory = prefs.getBoolean(context + .getString(R.string.sponsor_block_category_sponsor_key), false); + final boolean includeIntroCategory = prefs.getBoolean(context + .getString(R.string.sponsor_block_category_intro_key), false); + final boolean includeOutroCategory = prefs.getBoolean(context + .getString(R.string.sponsor_block_category_outro_key), false); + final boolean includeInteractionCategory = prefs.getBoolean(context + .getString(R.string.sponsor_block_category_interaction_key), false); + final boolean includeSelfPromoCategory = prefs.getBoolean(context + .getString(R.string.sponsor_block_category_self_promo_key), false); + final boolean includeMusicCategory = prefs.getBoolean(context + .getString(R.string.sponsor_block_category_non_music_key), false); + final ArrayList categoryParamList = new ArrayList<>(); if (includeSponsorCategory) { @@ -66,7 +96,7 @@ public final class SponsorBlockUtils { String categoryParams = "[\"" + TextUtils.join("\",\"", categoryParamList) + "\"]"; categoryParams = URLEncoder.encode(categoryParams, "utf-8"); - final String videoIdHash = toSha256(videoId); + final String videoIdHash = toSha256(streamInfo.getId()); if (videoIdHash == null) { return null; @@ -106,7 +136,7 @@ public final class SponsorBlockUtils { final JsonObject jObj1 = (JsonObject) obj1; final String responseVideoId = jObj1.getString("videoID"); - if (!responseVideoId.equals(videoId)) { + if (!responseVideoId.equals(streamInfo.getId())) { continue; }