-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.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());

View File

@ -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() {

View File

@ -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());

View File

@ -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<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 {
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<StreamInfo> syncPlayback = new Consumer<StreamInfo>() {
@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));
}

View File

@ -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();
}