diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 479a73347..63637cf16 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -32,6 +32,7 @@ import android.os.Build; import android.os.IBinder; import android.os.PowerManager; import android.support.annotation.IntRange; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.util.Log; @@ -49,6 +50,7 @@ import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.ThemeHelper; @@ -346,7 +348,7 @@ public final class BackgroundPlayer extends Service { public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) { resetNotification(); if (bigNotRemoteView != null) { - if (currentInfo != null) { + if (currentItem != null) { bigNotRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle()); bigNotRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName()); } @@ -354,7 +356,7 @@ public final class BackgroundPlayer extends Service { bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration)); } if (notRemoteView != null) { - if (currentInfo != null) { + if (currentItem != null) { notRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle()); notRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName()); } @@ -442,8 +444,8 @@ public final class BackgroundPlayer extends Service { //////////////////////////////////////////////////////////////////////////*/ @Override - public void sync(@Nullable final StreamInfo info) { - super.sync(info); + public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) { + super.sync(item, info); resetNotification(); notRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle()); diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 6bdff821b..bcbfb875a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -79,6 +79,7 @@ import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueueAdapter; +import org.schabi.newpipe.playlist.PlayQueueItem; import java.io.File; import java.io.Serializable; @@ -149,6 +150,7 @@ public abstract class BasePlayer implements Player.EventListener, private long videoPos = -1; protected StreamInfo currentInfo; + protected PlayQueueItem currentItem; /*////////////////////////////////////////////////////////////////////////// // Player @@ -729,23 +731,27 @@ public abstract class BasePlayer implements Player.EventListener, if (getCurrentState() == STATE_BLOCKED) changeState(STATE_BUFFERING); simpleExoPlayer.prepare(mediaSource); + simpleExoPlayer.seekToDefaultPosition(); } @Override - public void sync(@Nullable final StreamInfo info) { - if (info == null || simpleExoPlayer == null) return; + public void sync(@android.support.annotation.NonNull final PlayQueueItem item, + @Nullable final StreamInfo info) { + if (simpleExoPlayer == null) return; if (DEBUG) Log.d(TAG, "Syncing..."); + currentItem = item; + currentInfo = info; + // Check if on wrong window final int currentSourceIndex = playQueue.getIndex(); - if (!(simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex)) { - final long startPos = currentInfo != null ? currentInfo.start_position : 0; + if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex) { + final long startPos = info != null ? info.start_position : 0; if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos)); simpleExoPlayer.seekTo(currentSourceIndex, startPos); } - currentInfo = info; - initThumbnail(info.thumbnail_url); + initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url); } @Override @@ -797,13 +803,14 @@ public abstract class BasePlayer implements Player.EventListener, } public void onPlayPrevious() { - if (simpleExoPlayer == null || playQueue == null || currentInfo == null) return; + if (simpleExoPlayer == null || playQueue == null) return; if (DEBUG) Log.d(TAG, "onPlayPrevious() called"); /* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT milliseconds, restart current track. * Also restart the track if the current track is the first in a queue.*/ if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT || playQueue.getIndex() == 0) { - simpleExoPlayer.seekTo(currentInfo.start_position); + final long startPos = currentInfo == null ? 0 : currentInfo.start_position; + simpleExoPlayer.seekTo(startPos); } else { playQueue.offsetIndex(-1); } @@ -947,15 +954,15 @@ public abstract class BasePlayer implements Player.EventListener, } public String getVideoUrl() { - return currentInfo == null ? null : currentInfo.url; + return currentItem == null ? null : currentItem.getUrl(); } public String getVideoTitle() { - return currentInfo == null ? null : currentInfo.name; + return currentItem == null ? null : currentItem.getTitle(); } public String getUploaderName() { - return currentInfo == null ? null : currentInfo.uploader_name; + return currentItem == null ? null : currentItem.getUploader(); } public boolean isCompleted() { diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index d720c8c61..2f30f4ec2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -26,6 +26,7 @@ import android.graphics.Color; import android.media.AudioManager; import android.os.Build; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import android.view.GestureDetector; @@ -43,6 +44,7 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; @@ -250,8 +252,8 @@ public final class MainVideoPlayer extends Activity { } @Override - public void sync(@Nullable final StreamInfo info) { - super.sync(info); + public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) { + super.sync(item, info); titleTextView.setText(getVideoTitle()); channelTextView.setText(getUploaderName()); diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index 482503cb6..29df0323e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -31,6 +31,7 @@ import android.graphics.Color; import android.graphics.PorterDuff; import android.os.Build; import android.os.Handler; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.util.Log; @@ -64,6 +65,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; +import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.ListHelper; @@ -304,8 +306,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. //////////////////////////////////////////////////////////////////////////*/ @Override - public void sync(@Nullable final StreamInfo info) { - super.sync(info); + public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) { + super.sync(item, info); if (info != null) { final List videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false); 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 f40ce978a..e18bed63c 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 @@ -29,7 +29,7 @@ import io.reactivex.functions.Consumer; public class MediaSourceManager implements DeferredMediaSource.Callback { private final String TAG = "MediaSourceManager@" + Integer.toHexString(hashCode()); // One-side rolling window size for default loading - // Effectively loads WINDOW_SIZE * 2 + 1 streams, should be at least 1 to ensure gapless playback + // Effectively loads WINDOW_SIZE * 2 + 1 streams, must be greater than 0 // todo: inject this parameter, allow user settings perhaps private static final int WINDOW_SIZE = 1; @@ -116,7 +116,6 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { * */ public void reset() { tryBlock(); - resetSources(); populateSources(); } @@ -149,6 +148,10 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { private void onPlayQueueChanged(final PlayQueueMessage event) { // why no pattern matching in Java =( switch (event.type()) { + case INIT: + case REORDER: + reset(); + break; case APPEND: populateSources(); break; @@ -159,10 +162,6 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { final RemoveEvent removeEvent = (RemoveEvent) event; remove(removeEvent.index()); break; - case INIT: - case REORDER: - reset(); - break; case MOVE: final MoveEvent moveEvent = (MoveEvent) event; move(moveEvent.getFromIndex(), moveEvent.getToIndex()); @@ -195,6 +194,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { private boolean tryBlock() { if (!isBlocked) { playbackListener.block(); + resetSources(); isBlocked = true; return true; } @@ -202,7 +202,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { } private boolean tryUnblock() { - if (isPlayQueueReady() && isBlocked) { + if (isPlayQueueReady() && isBlocked && sources != null) { isBlocked = false; playbackListener.unblock(sources); return true; @@ -216,7 +216,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { final Consumer syncPlayback = new Consumer() { @Override public void accept(StreamInfo streamInfo) throws Exception { - playbackListener.sync(streamInfo); + playbackListener.sync(currentItem, streamInfo); } }; @@ -224,7 +224,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { @Override public void accept(Throwable throwable) throws Exception { Log.e(TAG, "Sync error:", throwable); - playbackListener.sync(null); + playbackListener.sync(currentItem,null); } }; @@ -244,11 +244,12 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { private void resetSources() { if (this.sources != null) this.sources.releaseSource(); - this.sources = new DynamicConcatenatingMediaSource(); } private void populateSources() { + if (sources == null) return; + for (final PlayQueueItem item : playQueue.getStreams()) { insert(playQueue.indexOf(item), new DeferredMediaSource(item, this)); } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java index 7718437c7..391c15bbe 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java @@ -1,54 +1,56 @@ package org.schabi.newpipe.player.playback; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.google.android.exoplayer2.source.MediaSource; import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.playlist.PlayQueueItem; import java.util.List; public interface PlaybackListener { - /* - * Called when the stream at the current queue index is not ready yet. - * Signals to the listener to block the player from playing anything. - * - * May be called at any time. - * */ + /** + * Called when the stream at the current queue index is not ready yet. + * Signals to the listener to block the player from playing anything and notify the source + * is now invalid. + * + * May be called at any time. + * */ void block(); - /* - * Called when the stream at the current queue index is ready. - * Signals to the listener to resume the player. - * - * May be called only when the player is blocked. - * */ + /** + * Called when the stream at the current queue index is ready. + * Signals to the listener to resume the player by preparing a new source. + * + * May be called only when the player is blocked. + * */ void unblock(final MediaSource mediaSource); - /* - * Called when the queue index is refreshed. - * Signals to the listener to synchronize the player's window to the manager's - * window. - * - * May be null. - * May be called only after playback is unblocked. - * */ - void sync(@Nullable final StreamInfo info); + /** + * Called when the queue index is refreshed. + * Signals to the listener to synchronize the player's window to the manager's + * window. + * + * May be called only after unblock is called. + * */ + void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info); - /* - * Requests the listener to resolve a stream info into a media source - * according to the listener's implementation (background, popup or main video player). - * - * May be called at any time. - * */ + /** + * Requests the listener to resolve a stream info into a media source + * according to the listener's implementation (background, popup or main video player). + * + * May be called at any time. + * */ MediaSource sourceOf(final StreamInfo info); - /* - * Called when the play queue can no longer to played or used. - * Currently, this means the play queue is empty and complete. - * Signals to the listener that it should shutdown. - * - * May be called at any time. - * */ + /** + * Called when the play queue can no longer to played or used. + * Currently, this means the play queue is empty and complete. + * Signals to the listener that it should shutdown. + * + * May be called at any time. + * */ void shutdown(); }