-Fixed incorrect stream from being played after consecutive player errors.

-Fixed MediaSource reuse due to MediaSourceManager not resetting source on block.
This commit is contained in:
John Zhen M 2017-10-09 17:25:45 -07:00 committed by John Zhen Mo
parent 2e414cfd63
commit f1e52b8b92
6 changed files with 79 additions and 63 deletions

View file

@ -32,6 +32,7 @@ import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
import android.support.annotation.IntRange; import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.util.Log; 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.StreamingService;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.ThemeHelper; 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) { public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
resetNotification(); resetNotification();
if (bigNotRemoteView != null) { if (bigNotRemoteView != null) {
if (currentInfo != null) { if (currentItem != null) {
bigNotRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle()); bigNotRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle());
bigNotRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName()); 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)); bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration));
} }
if (notRemoteView != null) { if (notRemoteView != null) {
if (currentInfo != null) { if (currentItem != null) {
notRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle()); notRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle());
notRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName()); notRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName());
} }
@ -442,8 +444,8 @@ public final class BackgroundPlayer extends Service {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void sync(@Nullable final StreamInfo info) { public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
super.sync(info); super.sync(item, info);
resetNotification(); resetNotification();
notRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle()); notRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle());

View file

@ -79,6 +79,7 @@ import org.schabi.newpipe.player.playback.MediaSourceManager;
import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.player.playback.PlaybackListener;
import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueAdapter; import org.schabi.newpipe.playlist.PlayQueueAdapter;
import org.schabi.newpipe.playlist.PlayQueueItem;
import java.io.File; import java.io.File;
import java.io.Serializable; import java.io.Serializable;
@ -149,6 +150,7 @@ public abstract class BasePlayer implements Player.EventListener,
private long videoPos = -1; private long videoPos = -1;
protected StreamInfo currentInfo; protected StreamInfo currentInfo;
protected PlayQueueItem currentItem;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Player // Player
@ -729,23 +731,27 @@ public abstract class BasePlayer implements Player.EventListener,
if (getCurrentState() == STATE_BLOCKED) changeState(STATE_BUFFERING); if (getCurrentState() == STATE_BLOCKED) changeState(STATE_BUFFERING);
simpleExoPlayer.prepare(mediaSource); simpleExoPlayer.prepare(mediaSource);
simpleExoPlayer.seekToDefaultPosition();
} }
@Override @Override
public void sync(@Nullable final StreamInfo info) { public void sync(@android.support.annotation.NonNull final PlayQueueItem item,
if (info == null || simpleExoPlayer == null) return; @Nullable final StreamInfo info) {
if (simpleExoPlayer == null) return;
if (DEBUG) Log.d(TAG, "Syncing..."); if (DEBUG) Log.d(TAG, "Syncing...");
currentItem = item;
currentInfo = info;
// Check if on wrong window // Check if on wrong window
final int currentSourceIndex = playQueue.getIndex(); final int currentSourceIndex = playQueue.getIndex();
if (!(simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex)) { if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex) {
final long startPos = currentInfo != null ? currentInfo.start_position : 0; final long startPos = info != null ? info.start_position : 0;
if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos)); if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos));
simpleExoPlayer.seekTo(currentSourceIndex, startPos); simpleExoPlayer.seekTo(currentSourceIndex, startPos);
} }
currentInfo = info; initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url);
initThumbnail(info.thumbnail_url);
} }
@Override @Override
@ -797,13 +803,14 @@ public abstract class BasePlayer implements Player.EventListener,
} }
public void onPlayPrevious() { 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 (DEBUG) Log.d(TAG, "onPlayPrevious() called");
/* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT milliseconds, restart current track. /* 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.*/ * Also restart the track if the current track is the first in a queue.*/
if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT || playQueue.getIndex() == 0) { 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 { } else {
playQueue.offsetIndex(-1); playQueue.offsetIndex(-1);
} }
@ -947,15 +954,15 @@ public abstract class BasePlayer implements Player.EventListener,
} }
public String getVideoUrl() { public String getVideoUrl() {
return currentInfo == null ? null : currentInfo.url; return currentItem == null ? null : currentItem.getUrl();
} }
public String getVideoTitle() { public String getVideoTitle() {
return currentInfo == null ? null : currentInfo.name; return currentItem == null ? null : currentItem.getTitle();
} }
public String getUploaderName() { public String getUploaderName() {
return currentInfo == null ? null : currentInfo.uploader_name; return currentItem == null ? null : currentItem.getUploader();
} }
public boolean isCompleted() { public boolean isCompleted() {

View file

@ -26,6 +26,7 @@ import android.graphics.Color;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import android.view.GestureDetector; import android.view.GestureDetector;
@ -43,6 +44,7 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.PermissionHelper;
@ -250,8 +252,8 @@ public final class MainVideoPlayer extends Activity {
} }
@Override @Override
public void sync(@Nullable final StreamInfo info) { public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
super.sync(info); super.sync(item, info);
titleTextView.setText(getVideoTitle()); titleTextView.setText(getVideoTitle());
channelTextView.setText(getUploaderName()); channelTextView.setText(getUploaderName());

View file

@ -31,6 +31,7 @@ import android.graphics.Color;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.util.Log; import android.util.Log;
@ -64,6 +65,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.ListHelper;
@ -304,8 +306,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void sync(@Nullable final StreamInfo info) { public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
super.sync(info); super.sync(item, info);
if (info != null) { if (info != null) {
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false); final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);

View file

@ -29,7 +29,7 @@ import io.reactivex.functions.Consumer;
public class MediaSourceManager implements DeferredMediaSource.Callback { public class MediaSourceManager implements DeferredMediaSource.Callback {
private final String TAG = "MediaSourceManager@" + Integer.toHexString(hashCode()); private final String TAG = "MediaSourceManager@" + Integer.toHexString(hashCode());
// One-side rolling window size for default loading // 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 // todo: inject this parameter, allow user settings perhaps
private static final int WINDOW_SIZE = 1; private static final int WINDOW_SIZE = 1;
@ -116,7 +116,6 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
* */ * */
public void reset() { public void reset() {
tryBlock(); tryBlock();
resetSources();
populateSources(); populateSources();
} }
@ -149,6 +148,10 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
private void onPlayQueueChanged(final PlayQueueMessage event) { private void onPlayQueueChanged(final PlayQueueMessage event) {
// why no pattern matching in Java =( // why no pattern matching in Java =(
switch (event.type()) { switch (event.type()) {
case INIT:
case REORDER:
reset();
break;
case APPEND: case APPEND:
populateSources(); populateSources();
break; break;
@ -159,10 +162,6 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
final RemoveEvent removeEvent = (RemoveEvent) event; final RemoveEvent removeEvent = (RemoveEvent) event;
remove(removeEvent.index()); remove(removeEvent.index());
break; break;
case INIT:
case REORDER:
reset();
break;
case MOVE: case MOVE:
final MoveEvent moveEvent = (MoveEvent) event; final MoveEvent moveEvent = (MoveEvent) event;
move(moveEvent.getFromIndex(), moveEvent.getToIndex()); move(moveEvent.getFromIndex(), moveEvent.getToIndex());
@ -195,6 +194,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
private boolean tryBlock() { private boolean tryBlock() {
if (!isBlocked) { if (!isBlocked) {
playbackListener.block(); playbackListener.block();
resetSources();
isBlocked = true; isBlocked = true;
return true; return true;
} }
@ -202,7 +202,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
} }
private boolean tryUnblock() { private boolean tryUnblock() {
if (isPlayQueueReady() && isBlocked) { if (isPlayQueueReady() && isBlocked && sources != null) {
isBlocked = false; isBlocked = false;
playbackListener.unblock(sources); playbackListener.unblock(sources);
return true; return true;
@ -216,7 +216,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
final Consumer<StreamInfo> syncPlayback = new Consumer<StreamInfo>() { final Consumer<StreamInfo> syncPlayback = new Consumer<StreamInfo>() {
@Override @Override
public void accept(StreamInfo streamInfo) throws Exception { 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 @Override
public void accept(Throwable throwable) throws Exception { public void accept(Throwable throwable) throws Exception {
Log.e(TAG, "Sync error:", throwable); 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() { private void resetSources() {
if (this.sources != null) this.sources.releaseSource(); if (this.sources != null) this.sources.releaseSource();
this.sources = new DynamicConcatenatingMediaSource(); this.sources = new DynamicConcatenatingMediaSource();
} }
private void populateSources() { private void populateSources() {
if (sources == null) return;
for (final PlayQueueItem item : playQueue.getStreams()) { for (final PlayQueueItem item : playQueue.getStreams()) {
insert(playQueue.indexOf(item), new DeferredMediaSource(item, this)); insert(playQueue.indexOf(item), new DeferredMediaSource(item, this));
} }

View file

@ -1,54 +1,56 @@
package org.schabi.newpipe.player.playback; package org.schabi.newpipe.player.playback;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.playlist.PlayQueueItem;
import java.util.List; import java.util.List;
public interface PlaybackListener { public interface PlaybackListener {
/* /**
* Called when the stream at the current queue index is not ready yet. * Called when the stream at the current queue index is not ready yet.
* Signals to the listener to block the player from playing anything. * Signals to the listener to block the player from playing anything and notify the source
* * is now invalid.
* May be called at any time. *
* */ * May be called at any time.
* */
void block(); void block();
/* /**
* Called when the stream at the current queue index is ready. * Called when the stream at the current queue index is ready.
* Signals to the listener to resume the player. * Signals to the listener to resume the player by preparing a new source.
* *
* May be called only when the player is blocked. * May be called only when the player is blocked.
* */ * */
void unblock(final MediaSource mediaSource); void unblock(final MediaSource mediaSource);
/* /**
* Called when the queue index is refreshed. * Called when the queue index is refreshed.
* Signals to the listener to synchronize the player's window to the manager's * Signals to the listener to synchronize the player's window to the manager's
* window. * window.
* *
* May be null. * May be called only after unblock is called.
* May be called only after playback is unblocked. * */
* */ void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info);
void sync(@Nullable final StreamInfo info);
/* /**
* Requests the listener to resolve a stream info into a media source * Requests the listener to resolve a stream info into a media source
* according to the listener's implementation (background, popup or main video player). * according to the listener's implementation (background, popup or main video player).
* *
* May be called at any time. * May be called at any time.
* */ * */
MediaSource sourceOf(final StreamInfo info); MediaSource sourceOf(final StreamInfo info);
/* /**
* Called when the play queue can no longer to played or used. * Called when the play queue can no longer to played or used.
* Currently, this means the play queue is empty and complete. * Currently, this means the play queue is empty and complete.
* Signals to the listener that it should shutdown. * Signals to the listener that it should shutdown.
* *
* May be called at any time. * May be called at any time.
* */ * */
void shutdown(); void shutdown();
} }