-Fixed memory leak due to permanent remote view bitmap references.

-Removed redundant code in popup player.
This commit is contained in:
John Zhen M 2017-09-07 13:01:02 -07:00 committed by John Zhen Mo
parent eb15c04254
commit 150c3b413a
10 changed files with 204 additions and 289 deletions

View File

@ -146,7 +146,7 @@ public class BackgroundPlayer extends Service {
private void onScreenOnOff(boolean on) { private void onScreenOnOff(boolean on) {
if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]"); if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
if (on) { if (on) {
if (basePlayerImpl.isPlaying() && !basePlayerImpl.isProgressLoopRunning.get()) basePlayerImpl.startProgressLoop(); if (basePlayerImpl.isPlaying() && !basePlayerImpl.isProgressLoopRunning()) basePlayerImpl.startProgressLoop();
} else basePlayerImpl.stopProgressLoop(); } else basePlayerImpl.stopProgressLoop();
} }
@ -212,7 +212,7 @@ public class BackgroundPlayer extends Service {
* *
* @param drawableId if != -1, sets the drawable with that id on the play/pause button * @param drawableId if != -1, sets the drawable with that id on the play/pause button
*/ */
private void updateNotification(int drawableId) { private synchronized void updateNotification(int drawableId) {
if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]"); if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
if (notBuilder == null) return; if (notBuilder == null) return;
if (drawableId != -1) { if (drawableId != -1) {
@ -275,19 +275,27 @@ public class BackgroundPlayer extends Service {
} }
@Override @Override
public void initThumbnail() { public void initThumbnail(final String url) {
if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail); if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail); if (bigNotRemoteView != null) bigNotRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
updateNotification(-1); updateNotification(-1);
super.initThumbnail(); super.initThumbnail(url);
} }
@Override @Override
public void onThumbnailReceived(Bitmap thumbnail) { public void onThumbnailReceived(Bitmap thumbnail) {
super.onThumbnailReceived(thumbnail); super.onThumbnailReceived(thumbnail);
if (thumbnail != null) { if (thumbnail != null) {
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, thumbnail); videoThumbnail = thumbnail;
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, thumbnail);
// rebuild notification here since remote view does not release bitmaps, causing memory leaks
// remove this line to see for yourself
notBuilder = createNotification();
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
updateNotification(-1); updateNotification(-1);
} }
} }
@ -303,7 +311,7 @@ public class BackgroundPlayer extends Service {
FAST_FORWARD_REWIND_AMOUNT = 10000; FAST_FORWARD_REWIND_AMOUNT = 10000;
} }
PROGRESS_LOOP_INTERVAL = 1000; PROGRESS_LOOP_INTERVAL = 1000;
basePlayerImpl.getPlayer().setVolume(1f); simpleExoPlayer.setVolume(1f);
} }
@Override @Override
@ -382,13 +390,13 @@ public class BackgroundPlayer extends Service {
public void sync(final StreamInfo info, final int sortedStreamsIndex) { public void sync(final StreamInfo info, final int sortedStreamsIndex) {
super.sync(info, sortedStreamsIndex); super.sync(info, sortedStreamsIndex);
basePlayerImpl.setVideoTitle(info.name); setVideoTitle(info.name);
basePlayerImpl.setUploaderName(info.uploader_name); setUploaderName(info.uploader_name);
notRemoteView.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle()); notRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle());
notRemoteView.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName()); notRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName());
bigNotRemoteView.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle()); bigNotRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle());
bigNotRemoteView.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName()); bigNotRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName());
updateNotification(-1); updateNotification(-1);
} }
@ -436,7 +444,7 @@ public class BackgroundPlayer extends Service {
onVideoPlayPause(); onVideoPlayPause();
break; break;
case ACTION_OPEN_DETAIL: case ACTION_OPEN_DETAIL:
onOpenDetail(BackgroundPlayer.this, basePlayerImpl.getVideoUrl(), basePlayerImpl.getVideoTitle()); onOpenDetail(BackgroundPlayer.this, getVideoUrl(), getVideoTitle());
break; break;
case ACTION_REPEAT: case ACTION_REPEAT:
onRepeatClicked(); onRepeatClicked();
@ -483,7 +491,7 @@ public class BackgroundPlayer extends Service {
super.onPaused(); super.onPaused();
updateNotification(R.drawable.ic_play_arrow_white); updateNotification(R.drawable.ic_play_arrow_white);
if (isProgressLoopRunning.get()) stopProgressLoop(); if (isProgressLoopRunning()) stopProgressLoop();
releaseWifiAndCpu(); releaseWifiAndCpu();
} }

View File

@ -30,12 +30,9 @@ import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.media.AudioManager; import android.media.AudioManager;
import android.net.Uri; import android.net.Uri;
import android.os.Handler;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.DefaultLoadControl;
@ -67,15 +64,14 @@ import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvicto
import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import org.schabi.newpipe.Downloader; import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
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.player.playback.PlaybackManager;
import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.player.playback.PlaybackListener;
import org.schabi.newpipe.player.playback.PlaybackManager;
import org.schabi.newpipe.playlist.ExternalPlayQueue; import org.schabi.newpipe.playlist.ExternalPlayQueue;
import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.PlayQueueItem;
@ -89,7 +85,19 @@ import java.util.ArrayList;
import java.util.Formatter; import java.util.Formatter;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.SingleObserver;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.SerialDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Predicate;
import io.reactivex.schedulers.Schedulers;
/** /**
* Base for the players, joining the common properties * Base for the players, joining the common properties
@ -101,7 +109,7 @@ public abstract class BasePlayer implements Player.EventListener,
AudioManager.OnAudioFocusChangeListener, PlaybackListener { AudioManager.OnAudioFocusChangeListener, PlaybackListener {
// TODO: Check api version for deprecated audio manager methods // TODO: Check api version for deprecated audio manager methods
public static final boolean DEBUG = false; public static final boolean DEBUG = true;
public static final String TAG = "BasePlayer"; public static final String TAG = "BasePlayer";
protected Context context; protected Context context;
@ -134,7 +142,6 @@ public abstract class BasePlayer implements Player.EventListener,
protected String videoUrl = ""; protected String videoUrl = "";
protected String videoTitle = ""; protected String videoTitle = "";
protected String videoThumbnailUrl = ""; protected String videoThumbnailUrl = "";
protected long videoStartPos = -1;
protected String uploaderName = ""; protected String uploaderName = "";
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -144,8 +151,8 @@ public abstract class BasePlayer implements Player.EventListener,
protected PlaybackManager playbackManager; protected PlaybackManager playbackManager;
protected PlayQueue playQueue; protected PlayQueue playQueue;
protected int restoreQueueIndex; protected int queueStartPos = 0;
protected long restoreWindowPos; protected long videoStartPos = -1;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Player // Player
@ -157,21 +164,19 @@ public abstract class BasePlayer implements Player.EventListener,
protected SimpleExoPlayer simpleExoPlayer; protected SimpleExoPlayer simpleExoPlayer;
protected boolean isPrepared = false; protected boolean isPrepared = false;
protected MediaSource mediaSource;
protected CacheDataSourceFactory cacheDataSourceFactory; protected CacheDataSourceFactory cacheDataSourceFactory;
protected final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); protected final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
protected final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); protected final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
protected int PROGRESS_LOOP_INTERVAL = 100; protected int PROGRESS_LOOP_INTERVAL = 100;
protected AtomicBoolean isProgressLoopRunning = new AtomicBoolean(); protected Disposable progressUpdateReactor;
protected Handler progressLoop;
protected Runnable progressUpdate; protected SerialDisposable thumbnailReactor;
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public BasePlayer(Context context) { public BasePlayer(Context context) {
this.context = context; this.context = context;
this.progressLoop = new Handler();
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
this.audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE)); this.audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
@ -184,6 +189,8 @@ public abstract class BasePlayer implements Player.EventListener,
this.intentFilter = new IntentFilter(); this.intentFilter = new IntentFilter();
setupBroadcastReceiver(intentFilter); setupBroadcastReceiver(intentFilter);
context.registerReceiver(broadcastReceiver, intentFilter); context.registerReceiver(broadcastReceiver, intentFilter);
this.thumbnailReactor = new SerialDisposable();
} }
public void setup() { public void setup() {
@ -223,23 +230,31 @@ public abstract class BasePlayer implements Player.EventListener,
simpleExoPlayer.addListener(this); simpleExoPlayer.addListener(this);
} }
public void initListeners() { public void initListeners() {}
progressUpdate = new Runnable() {
protected Disposable getProgressReactor() {
return Observable.interval(PROGRESS_LOOP_INTERVAL, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.filter(new Predicate<Long>() {
@Override @Override
public void run() { public boolean test(@NonNull Long aLong) throws Exception {
//if(DEBUG) Log.d(TAG, "progressUpdate run() called"); return isProgressLoopRunning();
onUpdateProgress((int) simpleExoPlayer.getCurrentPosition(), (int) simpleExoPlayer.getDuration(), simpleExoPlayer.getBufferedPercentage());
if (isProgressLoopRunning.get()) progressLoop.postDelayed(this, PROGRESS_LOOP_INTERVAL);
} }
}; })
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
triggerProgressUpdate();
}
});
} }
public void handleIntent(Intent intent) { public void handleIntent(Intent intent) {
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]"); if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
if (intent == null) return; if (intent == null) return;
restoreQueueIndex = intent.getIntExtra(RESTORE_QUEUE_INDEX, 0); queueStartPos = intent.getIntExtra(RESTORE_QUEUE_INDEX, 0);
restoreWindowPos = intent.getLongExtra(START_POSITION, 0); videoStartPos = intent.getLongExtra(START_POSITION, 0);
setPlaybackSpeed(intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed())); setPlaybackSpeed(intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed()));
switch (intent.getStringExtra(INTENT_TYPE)) { switch (intent.getStringExtra(INTENT_TYPE)) {
@ -254,7 +269,6 @@ public abstract class BasePlayer implements Player.EventListener,
} }
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void handleExternalPlaylistIntent(Intent intent) { public void handleExternalPlaylistIntent(Intent intent) {
final int serviceId = intent.getIntExtra(ExternalPlayQueue.SERVICE_ID, -1); final int serviceId = intent.getIntExtra(ExternalPlayQueue.SERVICE_ID, -1);
@ -286,30 +300,46 @@ public abstract class BasePlayer implements Player.EventListener,
playbackManager = new PlaybackManager(this, playQueue); playbackManager = new PlaybackManager(this, playQueue);
} }
public void initThumbnail() { public void initThumbnail(final String url) {
if (DEBUG) Log.d(TAG, "initThumbnail() called"); final Callable<Bitmap> bitmapCallable = new Callable<Bitmap>() {
videoThumbnail = null;
if (videoThumbnailUrl == null || videoThumbnailUrl.isEmpty()) return;
ImageLoader.getInstance().resume();
ImageLoader.getInstance().loadImage(videoThumbnailUrl, new SimpleImageLoadingListener() {
@Override @Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { public Bitmap call() throws Exception {
if (simpleExoPlayer == null) return; return ImageLoader.getInstance().loadImageSync(url);
if (DEBUG) }
Log.d(TAG, "onLoadingComplete() called with: imageUri = [" + imageUri + "], view = [" + view + "], loadedImage = [" + loadedImage + "]"); };
videoThumbnail = loadedImage;
onThumbnailReceived(loadedImage); Single.fromCallable(bitmapCallable)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Bitmap>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
thumbnailReactor.set(d);
}
@Override
public void onSuccess(@NonNull Bitmap bitmap) {
onThumbnailReceived(bitmap);
}
@Override
public void onError(@NonNull Throwable e) {
Log.e(TAG, "Thumbnail Fetch Failed.", e);
} }
}); });
} }
public void onThumbnailReceived(Bitmap thumbnail) {
if (DEBUG) Log.d(TAG, "onThumbnailReceived() called with: thumbnail = [" + thumbnail + "]");
}
public void destroyPlayer() { public void destroyPlayer() {
if (DEBUG) Log.d(TAG, "destroyPlayer() called"); if (DEBUG) Log.d(TAG, "destroyPlayer() called");
if (simpleExoPlayer != null) { if (simpleExoPlayer != null) {
simpleExoPlayer.stop(); simpleExoPlayer.stop();
simpleExoPlayer.release(); simpleExoPlayer.release();
} }
if (progressLoop != null && isProgressLoopRunning.get()) stopProgressLoop(); if (isProgressLoopRunning()) stopProgressLoop();
if (audioManager != null) { if (audioManager != null) {
audioManager.abandonAudioFocus(this); audioManager.abandonAudioFocus(this);
audioManager = null; audioManager = null;
@ -320,7 +350,11 @@ public abstract class BasePlayer implements Player.EventListener,
if (DEBUG) Log.d(TAG, "destroy() called"); if (DEBUG) Log.d(TAG, "destroy() called");
destroyPlayer(); destroyPlayer();
unregisterBroadcastReceiver(); unregisterBroadcastReceiver();
thumbnailReactor.dispose();
thumbnailReactor = null;
videoThumbnail = null; videoThumbnail = null;
simpleExoPlayer = null; simpleExoPlayer = null;
} }
@ -469,19 +503,19 @@ public abstract class BasePlayer implements Player.EventListener,
public void onLoading() { public void onLoading() {
if (DEBUG) Log.d(TAG, "onLoading() called"); if (DEBUG) Log.d(TAG, "onLoading() called");
if (!isProgressLoopRunning.get()) startProgressLoop(); if (!isProgressLoopRunning()) startProgressLoop();
} }
public void onPlaying() { public void onPlaying() {
if (DEBUG) Log.d(TAG, "onPlaying() called"); if (DEBUG) Log.d(TAG, "onPlaying() called");
if (!isProgressLoopRunning.get()) startProgressLoop(); if (!isProgressLoopRunning()) startProgressLoop();
} }
public void onBuffering() { public void onBuffering() {
} }
public void onPaused() { public void onPaused() {
if (isProgressLoopRunning.get()) stopProgressLoop(); if (isProgressLoopRunning()) stopProgressLoop();
} }
public void onPausedSeek() { public void onPausedSeek() {
@ -489,7 +523,7 @@ public abstract class BasePlayer implements Player.EventListener,
public void onCompleted() { public void onCompleted() {
if (DEBUG) Log.d(TAG, "onCompleted() called"); if (DEBUG) Log.d(TAG, "onCompleted() called");
if (isProgressLoopRunning.get()) stopProgressLoop(); if (isProgressLoopRunning()) stopProgressLoop();
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -524,22 +558,25 @@ public abstract class BasePlayer implements Player.EventListener,
@Override @Override
public void onTimelineChanged(Timeline timeline, Object manifest) { public void onTimelineChanged(Timeline timeline, Object manifest) {
if (DEBUG) Log.d(TAG, "onTimelineChanged(), timeline size = " + timeline.getWindowCount());
} }
@Override @Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
Log.w(TAG, "onTracksChanged() called, unsupported operation. Is this expected?");
} }
@Override @Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
if (DEBUG) Log.d(TAG, "playbackParameters(), speed: " + playbackParameters.speed + ", pitch: " + playbackParameters.pitch);
} }
@Override @Override
public void onLoadingChanged(boolean isLoading) { public void onLoadingChanged(boolean isLoading) {
if (DEBUG) Log.d(TAG, "onLoadingChanged() called with: isLoading = [" + isLoading + "]"); if (DEBUG) Log.d(TAG, "onLoadingChanged() called with: isLoading = [" + isLoading + "]");
if (!isLoading && getCurrentState() == STATE_PAUSED && isProgressLoopRunning.get()) stopProgressLoop(); if (!isLoading && getCurrentState() == STATE_PAUSED && isProgressLoopRunning()) stopProgressLoop();
else if (isLoading && !isProgressLoopRunning.get()) startProgressLoop(); else if (isLoading && !isProgressLoopRunning()) startProgressLoop();
} }
@Override @Override
@ -595,6 +632,11 @@ public abstract class BasePlayer implements Player.EventListener,
playbackManager.refresh(newIndex); playbackManager.refresh(newIndex);
} }
@Override
public void onRepeatModeChanged(int i) {
if (DEBUG) Log.d(TAG, "onRepeatModeChanged() called with: mode = [" + i + "]");
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Playback Listener // Playback Listener
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -614,13 +656,13 @@ public abstract class BasePlayer implements Player.EventListener,
if (simpleExoPlayer == null) return; if (simpleExoPlayer == null) return;
if (DEBUG) Log.d(TAG, "Unblocking..."); if (DEBUG) Log.d(TAG, "Unblocking...");
if (restoreQueueIndex != playQueue.getIndex()) { if (queueStartPos != playQueue.getIndex()) {
restoreQueueIndex = playQueue.getIndex(); queueStartPos = playQueue.getIndex();
restoreWindowPos = 0; videoStartPos = 0;
} }
simpleExoPlayer.prepare(playbackManager.getMediaSource()); simpleExoPlayer.prepare(playbackManager.getMediaSource());
simpleExoPlayer.seekTo(playbackManager.getCurrentSourceIndex(), restoreWindowPos); simpleExoPlayer.seekTo(playbackManager.getCurrentSourceIndex(), videoStartPos);
simpleExoPlayer.setPlayWhenReady(false); simpleExoPlayer.setPlayWhenReady(false);
} }
@ -633,15 +675,16 @@ public abstract class BasePlayer implements Player.EventListener,
videoThumbnailUrl = info.thumbnail_url; videoThumbnailUrl = info.thumbnail_url;
videoTitle = info.name; videoTitle = info.name;
initThumbnail(); initThumbnail(videoThumbnailUrl);
if (simpleExoPlayer.getCurrentWindowIndex() != playbackManager.getCurrentSourceIndex()) { if (simpleExoPlayer.getCurrentWindowIndex() != playbackManager.getCurrentSourceIndex()) {
if (DEBUG) Log.w(TAG, "Rewinding to correct window"); if (DEBUG) Log.w(TAG, "Rewinding to correct window");
if (simpleExoPlayer.getCurrentTimeline().getWindowCount() > playbackManager.getCurrentSourceIndex()) { if (simpleExoPlayer.getCurrentTimeline().getWindowCount() > playbackManager.getCurrentSourceIndex()) {
simpleExoPlayer.seekToDefaultPosition(playbackManager.getCurrentSourceIndex()); simpleExoPlayer.seekToDefaultPosition(playbackManager.getCurrentSourceIndex());
} else { } else {
Toast.makeText(context, "Play Queue out of sync", Toast.LENGTH_SHORT).show(); if (DEBUG) Log.w(TAG, "Play Queue out of sync");
simpleExoPlayer.seekToDefaultPosition(); playbackManager.reset();
return;
} }
} }
@ -674,26 +717,12 @@ public abstract class BasePlayer implements Player.EventListener,
public void onVideoPlayPause() { public void onVideoPlayPause() {
if (DEBUG) Log.d(TAG, "onVideoPlayPause() called"); if (DEBUG) Log.d(TAG, "onVideoPlayPause() called");
if (currentState == STATE_COMPLETED) {
onVideoPlayPauseRepeat();
return;
}
if (!isPlaying()) audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); if (!isPlaying()) audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
else audioManager.abandonAudioFocus(this); else audioManager.abandonAudioFocus(this);
simpleExoPlayer.setPlayWhenReady(!isPlaying()); simpleExoPlayer.setPlayWhenReady(!isPlaying());
} }
public void onVideoPlayPauseRepeat() {
if (DEBUG) Log.d(TAG, "onVideoPlayPauseRepeat() called");
changeState(STATE_LOADING);
setVideoStartPos(0);
simpleExoPlayer.seekTo(0);
simpleExoPlayer.setPlayWhenReady(true);
audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
}
public void onFastRewind() { public void onFastRewind() {
if (DEBUG) Log.d(TAG, "onFastRewind() called"); if (DEBUG) Log.d(TAG, "onFastRewind() called");
seekBy(-FAST_FORWARD_REWIND_AMOUNT); seekBy(-FAST_FORWARD_REWIND_AMOUNT);
@ -704,10 +733,6 @@ public abstract class BasePlayer implements Player.EventListener,
seekBy(FAST_FORWARD_REWIND_AMOUNT); seekBy(FAST_FORWARD_REWIND_AMOUNT);
} }
public void onThumbnailReceived(Bitmap thumbnail) {
if (DEBUG) Log.d(TAG, "onThumbnailReceived() called with: thumbnail = [" + thumbnail + "]");
}
public void seekBy(int milliSeconds) { public void seekBy(int milliSeconds) {
if (DEBUG) Log.d(TAG, "seekBy() called with: milliSeconds = [" + milliSeconds + "]"); if (DEBUG) Log.d(TAG, "seekBy() called with: milliSeconds = [" + milliSeconds + "]");
if (simpleExoPlayer == null || (isCompleted() && milliSeconds > 0) || ((milliSeconds < 0 && simpleExoPlayer.getCurrentPosition() == 0))) if (simpleExoPlayer == null || (isCompleted() && milliSeconds > 0) || ((milliSeconds < 0 && simpleExoPlayer.getCurrentPosition() == 0)))
@ -746,14 +771,13 @@ public abstract class BasePlayer implements Player.EventListener,
} }
protected void startProgressLoop() { protected void startProgressLoop() {
progressLoop.removeCallbacksAndMessages(null); if (progressUpdateReactor != null) progressUpdateReactor.dispose();
isProgressLoopRunning.set(true); progressUpdateReactor = getProgressReactor();
progressLoop.post(progressUpdate);
} }
protected void stopProgressLoop() { protected void stopProgressLoop() {
isProgressLoopRunning.set(false); if (progressUpdateReactor != null) progressUpdateReactor.dispose();
progressLoop.removeCallbacksAndMessages(null); progressUpdateReactor = null;
} }
protected void tryDeleteCacheFiles(Context context) { protected void tryDeleteCacheFiles(Context context) {
@ -902,4 +926,8 @@ public abstract class BasePlayer implements Player.EventListener,
public boolean isPlayerReady() { public boolean isPlayerReady() {
return currentState == STATE_PLAYING || currentState == STATE_COMPLETED || currentState == STATE_PAUSED; return currentState == STATE_PLAYING || currentState == STATE_COMPLETED || currentState == STATE_PAUSED;
} }
public boolean isProgressLoopRunning() {
return progressUpdateReactor != null && !progressUpdateReactor.isDisposed();
}
} }

View File

@ -217,7 +217,7 @@ public class MainVideoPlayer extends Activity {
MySimpleOnGestureListener listener = new MySimpleOnGestureListener(); MySimpleOnGestureListener listener = new MySimpleOnGestureListener();
gestureDetector = new GestureDetector(context, listener); gestureDetector = new GestureDetector(context, listener);
gestureDetector.setIsLongpressEnabled(false); gestureDetector.setIsLongpressEnabled(false);
playerImpl.getRootView().setOnTouchListener(listener); getRootView().setOnTouchListener(listener);
repeatButton.setOnClickListener(this); repeatButton.setOnClickListener(this);
playPauseButton.setOnClickListener(this); playPauseButton.setOnClickListener(this);
@ -252,8 +252,10 @@ public class MainVideoPlayer extends Activity {
@Override @Override
public void onFullScreenButtonClicked() { public void onFullScreenButtonClicked() {
super.onFullScreenButtonClicked();
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called"); if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
if (playerImpl.getPlayer() == null) return; if (simpleExoPlayer == null) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !PermissionHelper.checkSystemAlertWindowPermission(MainVideoPlayer.this)) { && !PermissionHelper.checkSystemAlertWindowPermission(MainVideoPlayer.this)) {
@ -261,11 +263,11 @@ public class MainVideoPlayer extends Activity {
return; return;
} }
context.startService(NavigationHelper.getOpenVideoPlayerIntent(context, PopupVideoPlayer.class, playerImpl)); context.startService(NavigationHelper.getOpenVideoPlayerIntent(context, PopupVideoPlayer.class, this));
if (playerImpl != null) playerImpl.destroyPlayer(); destroyPlayer();
((View) getControlAnimationView().getParent()).setVisibility(View.GONE); ((View) getControlAnimationView().getParent()).setVisibility(View.GONE);
MainVideoPlayer.this.finish(); finish();
} }
@Override @Override
@ -302,10 +304,10 @@ public class MainVideoPlayer extends Activity {
if (getCurrentState() != STATE_COMPLETED) { if (getCurrentState() != STATE_COMPLETED) {
getControlsVisibilityHandler().removeCallbacksAndMessages(null); getControlsVisibilityHandler().removeCallbacksAndMessages(null);
animateView(playerImpl.getControlsRoot(), true, 300, 0, new Runnable() { animateView(getControlsRoot(), true, 300, 0, new Runnable() {
@Override @Override
public void run() { public void run() {
if (getCurrentState() == STATE_PLAYING && !playerImpl.isSomePopupMenuVisible()) { if (getCurrentState() == STATE_PLAYING && !isSomePopupMenuVisible()) {
hideControls(300, DEFAULT_CONTROLS_HIDE_TIME); hideControls(300, DEFAULT_CONTROLS_HIDE_TIME);
} }
} }
@ -321,7 +323,7 @@ public class MainVideoPlayer extends Activity {
@Override @Override
public void onStopTrackingTouch(SeekBar seekBar) { public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar); super.onStopTrackingTouch(seekBar);
if (playerImpl.wasPlaying()) { if (wasPlaying()) {
hideControls(100, 0); hideControls(100, 0);
} }
} }
@ -457,11 +459,6 @@ public class MainVideoPlayer extends Activity {
public ImageButton getPlayPauseButton() { public ImageButton getPlayPauseButton() {
return playPauseButton; return playPauseButton;
} }
@Override
public void onRepeatModeChanged(int i) {
}
} }
private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener { private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {

View File

@ -67,6 +67,10 @@ import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.old.PlayVideoActivity; import org.schabi.newpipe.player.old.PlayVideoActivity;
import org.schabi.newpipe.player.playback.PlaybackManager;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
@ -115,14 +119,7 @@ public class PopupVideoPlayer extends Service {
private float minimumWidth, minimumHeight; private float minimumWidth, minimumHeight;
private float maximumWidth, maximumHeight; private float maximumWidth, maximumHeight;
private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha";
private NotificationManager notificationManager; private NotificationManager notificationManager;
private NotificationCompat.Builder notBuilder;
private RemoteViews notRemoteView;
private ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build();
private VideoPlayerImpl playerImpl; private VideoPlayerImpl playerImpl;
private Disposable currentWorker; private Disposable currentWorker;
@ -148,7 +145,6 @@ public class PopupVideoPlayer extends Service {
if (playerImpl.getPlayer() == null) initPopup(); if (playerImpl.getPlayer() == null) initPopup();
if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true); if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true);
if (imageLoader != null) imageLoader.clearMemoryCache();
if (intent.getStringExtra(Constants.KEY_URL) != null) { if (intent.getStringExtra(Constants.KEY_URL) != null) {
final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0); final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
final String url = intent.getStringExtra(Constants.KEY_URL); final String url = intent.getStringExtra(Constants.KEY_URL);
@ -245,61 +241,6 @@ public class PopupVideoPlayer extends Service {
windowManager.addView(rootView, windowLayoutParams); windowManager.addView(rootView, windowLayoutParams);
} }
/*//////////////////////////////////////////////////////////////////////////
// Notification
//////////////////////////////////////////////////////////////////////////*/
private NotificationCompat.Builder createNotification() {
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_popup_notification);
if (playerImpl.getVideoThumbnail() == null) notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
else notRemoteView.setImageViewBitmap(R.id.notificationCover, playerImpl.getVideoThumbnail());
notRemoteView.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle());
notRemoteView.setTextViewText(R.id.notificationArtist, playerImpl.getUploaderName());
notRemoteView.setOnClickPendingIntent(R.id.notificationPlayPause,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
notRemoteView.setOnClickPendingIntent(R.id.notificationStop,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
notRemoteView.setOnClickPendingIntent(R.id.notificationContent,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_OPEN_DETAIL), PendingIntent.FLAG_UPDATE_CURRENT));
notRemoteView.setOnClickPendingIntent(R.id.notificationRepeat,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
switch (playerImpl.simpleExoPlayer.getRepeatMode()) {
case Player.REPEAT_MODE_OFF:
notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 77);
break;
case Player.REPEAT_MODE_ONE:
//todo change image
notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 168);
break;
case Player.REPEAT_MODE_ALL:
notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
break;
}
return new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
.setOngoing(true)
.setSmallIcon(R.drawable.ic_play_arrow_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContent(notRemoteView);
}
/**
* Updates the notification, and the play/pause button in it.
* Used for changes on the remoteView
*
* @param drawableId if != -1, sets the drawable with that id on the play/pause button
*/
private void updateNotification(int drawableId) {
if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
if (notBuilder == null || notRemoteView == null) return;
if (drawableId != -1) notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Misc // Misc
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -400,25 +341,22 @@ public class PopupVideoPlayer extends Service {
@Override @Override
public void destroy() { public void destroy() {
super.destroy(); super.destroy();
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, null);
} }
@Override @Override
public void onThumbnailReceived(Bitmap thumbnail) { public void onThumbnailReceived(Bitmap thumbnail) {
super.onThumbnailReceived(thumbnail); super.onThumbnailReceived(thumbnail);
if (thumbnail != null) {
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, thumbnail);
updateNotification(-1);
}
} }
@Override @Override
public void onFullScreenButtonClicked() { public void onFullScreenButtonClicked() {
super.onFullScreenButtonClicked();
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called"); if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
Intent intent; Intent intent;
if (!getSharedPreferences().getBoolean(getResources().getString(R.string.use_old_player_key), false)) { if (!getSharedPreferences().getBoolean(getResources().getString(R.string.use_old_player_key), false)) {
intent = NavigationHelper.getOpenVideoPlayerIntent(context, MainVideoPlayer.class, playerImpl); intent = NavigationHelper.getOpenVideoPlayerIntent(context, MainVideoPlayer.class, this);
if (!playerImpl.isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false); if (!isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else { } else {
intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class) intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class)
@ -429,31 +367,10 @@ public class PopupVideoPlayer extends Service {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} }
context.startActivity(intent); context.startActivity(intent);
if (playerImpl != null) playerImpl.destroyPlayer(); destroyPlayer();
stopSelf(); stopSelf();
} }
@Override
public void onRepeatClicked() {
super.onRepeatClicked();
switch (simpleExoPlayer.getRepeatMode()) {
case Player.REPEAT_MODE_OFF:
// Drawable didn't work on low API :/
//notRemoteView.setImageViewResource(R.id.notificationRepeat, R.drawable.ic_repeat_disabled_white);
// Set the icon to 30% opacity - 255 (max) * .3
notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 77);
break;
case Player.REPEAT_MODE_ONE:
// todo change image
notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 168);
break;
case Player.REPEAT_MODE_ALL:
notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
break;
}
updateNotification(-1);
}
@Override @Override
public void onDismiss(PopupMenu menu) { public void onDismiss(PopupMenu menu) {
super.onDismiss(menu); super.onDismiss(menu);
@ -469,7 +386,7 @@ public class PopupVideoPlayer extends Service {
@Override @Override
public void onStopTrackingTouch(SeekBar seekBar) { public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar); super.onStopTrackingTouch(seekBar);
if (playerImpl.wasPlaying()) { if (wasPlaying()) {
hideControls(100, 0); hideControls(100, 0);
} }
} }
@ -507,13 +424,13 @@ public class PopupVideoPlayer extends Service {
onVideoClose(); onVideoClose();
break; break;
case ACTION_PLAY_PAUSE: case ACTION_PLAY_PAUSE:
playerImpl.onVideoPlayPause(); onVideoPlayPause();
break; break;
case ACTION_OPEN_DETAIL: case ACTION_OPEN_DETAIL:
onOpenDetail(PopupVideoPlayer.this, playerImpl.getVideoUrl(), playerImpl.getVideoTitle()); onOpenDetail(PopupVideoPlayer.this, getVideoUrl(), getVideoTitle());
break; break;
case ACTION_REPEAT: case ACTION_REPEAT:
playerImpl.onRepeatClicked(); onRepeatClicked();
break; break;
} }
} }
@ -524,38 +441,32 @@ public class PopupVideoPlayer extends Service {
@Override @Override
public void onLoading() { public void onLoading() {
super.onLoading(); super.onLoading();
updateNotification(R.drawable.ic_play_arrow_white);
} }
@Override @Override
public void onPlaying() { public void onPlaying() {
super.onPlaying(); super.onPlaying();
updateNotification(R.drawable.ic_pause_white);
} }
@Override @Override
public void onBuffering() { public void onBuffering() {
super.onBuffering(); super.onBuffering();
updateNotification(R.drawable.ic_play_arrow_white);
} }
@Override @Override
public void onPaused() { public void onPaused() {
super.onPaused(); super.onPaused();
updateNotification(R.drawable.ic_play_arrow_white);
showAndAnimateControl(R.drawable.ic_play_arrow_white, false); showAndAnimateControl(R.drawable.ic_play_arrow_white, false);
} }
@Override @Override
public void onPausedSeek() { public void onPausedSeek() {
super.onPausedSeek(); super.onPausedSeek();
updateNotification(R.drawable.ic_play_arrow_white);
} }
@Override @Override
public void onCompleted() { public void onCompleted() {
super.onCompleted(); super.onCompleted();
updateNotification(R.drawable.ic_replay_white);
showAndAnimateControl(R.drawable.ic_replay_white, false); showAndAnimateControl(R.drawable.ic_replay_white, false);
} }
@ -564,10 +475,6 @@ public class PopupVideoPlayer extends Service {
public TextView getResizingIndicator() { public TextView getResizingIndicator() {
return resizingIndicator; return resizingIndicator;
} }
@Override
public void onRepeatModeChanged(int i) {
}
} }
private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener { private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
@ -746,49 +653,16 @@ public class PopupVideoPlayer extends Service {
this.serviceId = serviceId; this.serviceId = serviceId;
} }
public void onReceive(StreamInfo info) { public void onReceive(final StreamInfo info) {
playerImpl.setVideoTitle(info.name);
playerImpl.setVideoUrl(info.url);
playerImpl.setVideoThumbnailUrl(info.thumbnail_url);
playerImpl.setUploaderName(info.uploader_name);
playerImpl.setVideoStreamsList(new ArrayList<>(ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false)));
playerImpl.setAudioStream(ListHelper.getHighestQualityAudio(info.audio_streams));
int defaultResolution = ListHelper.getPopupDefaultResolutionIndex(context, playerImpl.getVideoStreamsList());
playerImpl.setSelectedIndexStream(defaultResolution);
if (DEBUG) {
Log.d(TAG, "FetcherHandler.StreamExtractor: chosen = "
+ MediaFormat.getNameById(info.video_streams.get(defaultResolution).format) + " "
+ info.video_streams.get(defaultResolution).resolution + " > "
+ info.video_streams.get(defaultResolution).url);
}
if (info.start_position > 0) playerImpl.setVideoStartPos(info.start_position * 1000); if (info.start_position > 0) playerImpl.setVideoStartPos(info.start_position * 1000);
else playerImpl.setVideoStartPos(-1); else playerImpl.setVideoStartPos(-1);
mainHandler.post(new Runnable() { mainHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
playerImpl.playQueue = new SinglePlayQueue(info, PlayQueueItem.DEFAULT_QUALITY);
playerImpl.playQueue.init(); playerImpl.playQueue.init();
} playerImpl.playbackManager = new PlaybackManager(playerImpl, playerImpl.playQueue);
});
imageLoader.resume();
imageLoader.loadImage(info.thumbnail_url, displayImageOptions, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(final String imageUri, View view, final Bitmap loadedImage) {
if (playerImpl == null || playerImpl.getPlayer() == null) return;
if (DEBUG) Log.d(TAG, "FetcherHandler.imageLoader.onLoadingComplete() called with: imageUri = [" + imageUri + "]");
mainHandler.post(new Runnable() {
@Override
public void run() {
playerImpl.setVideoThumbnail(loadedImage);
if (loadedImage != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
updateNotification(-1);
}
});
} }
}); });
} }

View File

@ -309,7 +309,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
public void onLoading() { public void onLoading() {
if (DEBUG) Log.d(TAG, "onLoading() called"); if (DEBUG) Log.d(TAG, "onLoading() called");
if (!isProgressLoopRunning.get()) startProgressLoop(); if (!isProgressLoopRunning()) startProgressLoop();
controlsVisibilityHandler.removeCallbacksAndMessages(null); controlsVisibilityHandler.removeCallbacksAndMessages(null);
animateView(controlsRoot, false, 300); animateView(controlsRoot, false, 300);
@ -331,7 +331,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
@Override @Override
public void onPlaying() { public void onPlaying() {
if (DEBUG) Log.d(TAG, "onPlaying() called"); if (DEBUG) Log.d(TAG, "onPlaying() called");
if (!isProgressLoopRunning.get()) startProgressLoop(); if (!isProgressLoopRunning()) startProgressLoop();
showAndAnimateControl(-1, true); showAndAnimateControl(-1, true);
loadingPanel.setVisibility(View.GONE); loadingPanel.setVisibility(View.GONE);
showControlsThenHide(); showControlsThenHide();
@ -362,7 +362,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
public void onCompleted() { public void onCompleted() {
if (DEBUG) Log.d(TAG, "onCompleted() called"); if (DEBUG) Log.d(TAG, "onCompleted() called");
if (isProgressLoopRunning.get()) stopProgressLoop(); if (isProgressLoopRunning()) stopProgressLoop();
showControls(500); showControls(500);
animateView(endScreen, true, 800); animateView(endScreen, true, 800);
@ -445,22 +445,15 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
} }
} }
@Override
public void onVideoPlayPauseRepeat() {
if (DEBUG) Log.d(TAG, "onVideoPlayPauseRepeat() called");
if (qualityChanged) {
setVideoStartPos(0);
//play(true);
} else super.onVideoPlayPauseRepeat();
}
@Override @Override
public void onThumbnailReceived(Bitmap thumbnail) { public void onThumbnailReceived(Bitmap thumbnail) {
super.onThumbnailReceived(thumbnail); super.onThumbnailReceived(thumbnail);
if (thumbnail != null) endScreen.setImageBitmap(thumbnail); if (thumbnail != null) endScreen.setImageBitmap(thumbnail);
} }
protected abstract void onFullScreenButtonClicked(); protected void onFullScreenButtonClicked() {
if (!isPlayerReady()) return;
}
@Override @Override
public void onFastRewind() { public void onFastRewind() {
@ -501,8 +494,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (qualityPopupMenuGroupId == menuItem.getGroupId()) { if (qualityPopupMenuGroupId == menuItem.getGroupId()) {
if (selectedIndexStream == menuItem.getItemId()) return true; if (selectedIndexStream == menuItem.getItemId()) return true;
restoreQueueIndex = playQueue.getIndex(); queueStartPos = playQueue.getIndex();
restoreWindowPos = simpleExoPlayer.getCurrentPosition(); videoStartPos = simpleExoPlayer.getCurrentPosition();
playbackManager.updateCurrent(menuItem.getItemId()); playbackManager.updateCurrent(menuItem.getItemId());
qualityTextView.setText(menuItem.getTitle()); qualityTextView.setText(menuItem.getTitle());
@ -580,7 +573,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200); animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
if (getCurrentState() == STATE_PAUSED_SEEK) changeState(STATE_BUFFERING); if (getCurrentState() == STATE_PAUSED_SEEK) changeState(STATE_BUFFERING);
if (!isProgressLoopRunning.get()) startProgressLoop(); if (!isProgressLoopRunning()) startProgressLoop();
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////

View File

@ -12,7 +12,7 @@ import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.events.PlayQueueMessage; import org.schabi.newpipe.playlist.events.PlayQueueMessage;
import org.schabi.newpipe.playlist.events.RemoveEvent; import org.schabi.newpipe.playlist.events.RemoveEvent;
import org.schabi.newpipe.playlist.events.SwapEvent; import org.schabi.newpipe.playlist.events.MoveEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -91,7 +91,9 @@ public class PlaybackManager {
public void report(final Exception error) { public void report(final Exception error) {
// ignore error checking for now, just remove the current index // ignore error checking for now, just remove the current index
if (error == null || !tryBlock()) return; if (error == null) return;
tryBlock();
final int index = playQueue.getIndex(); final int index = playQueue.getIndex();
playQueue.remove(index); playQueue.remove(index);
@ -101,7 +103,7 @@ public class PlaybackManager {
} }
public void updateCurrent(final int newSortedStreamsIndex) { public void updateCurrent(final int newSortedStreamsIndex) {
if (!tryBlock()) return; tryBlock();
PlayQueueItem item = playQueue.getCurrent(); PlayQueueItem item = playQueue.getCurrent();
item.setSortedQualityIndex(newSortedStreamsIndex); item.setSortedQualityIndex(newSortedStreamsIndex);
@ -110,6 +112,13 @@ public class PlaybackManager {
load(); load();
} }
public void reset() {
tryBlock();
resetSources();
load();
}
public void dispose() { public void dispose() {
if (playQueueReactor != null) playQueueReactor.cancel(); if (playQueueReactor != null) playQueueReactor.cancel();
if (disposables != null) disposables.dispose(); if (disposables != null) disposables.dispose();
@ -143,8 +152,8 @@ public class PlaybackManager {
switch (event.type()) { switch (event.type()) {
case INIT: case INIT:
isBlocked = true; isBlocked = true;
break;
case APPEND: case APPEND:
load();
break; break;
case SELECT: case SELECT:
onSelect(); onSelect();
@ -153,10 +162,9 @@ public class PlaybackManager {
final RemoveEvent removeEvent = (RemoveEvent) event; final RemoveEvent removeEvent = (RemoveEvent) event;
remove(removeEvent.index()); remove(removeEvent.index());
break; break;
case SWAP: case MOVE:
final SwapEvent swapEvent = (SwapEvent) event; final MoveEvent moveEvent = (MoveEvent) event;
swap(swapEvent.getFrom(), swapEvent.getTo()); move(moveEvent.getFrom(), moveEvent.getTo());
load();
break; break;
default: default:
break; break;
@ -167,6 +175,8 @@ public class PlaybackManager {
playQueue.fetch(); playQueue.fetch();
} else if (playQueue.isEmpty()) { } else if (playQueue.isEmpty()) {
playbackListener.shutdown(); playbackListener.shutdown();
} else {
load(); // All event warrants a load
} }
if (playQueueReactor != null) playQueueReactor.request(1); if (playQueueReactor != null) playQueueReactor.request(1);
@ -176,9 +186,7 @@ public class PlaybackManager {
public void onError(@NonNull Throwable e) {} public void onError(@NonNull Throwable e) {}
@Override @Override
public void onComplete() { public void onComplete() {}
dispose();
}
}; };
} }
@ -214,21 +222,26 @@ public class PlaybackManager {
/* /*
* Responds to a SELECT event. * Responds to a SELECT event.
* If the selected item is already loaded, then we simply synchronize and *
* If the player is being blocked, then nothing should happen.
*
* Otherwise:
*
* When the selected item is already loaded, then we simply synchronize and
* start loading some more items. * start loading some more items.
* *
* If the current item has not been fully loaded, then the player will be * When the current item has not been fully loaded, then the player will be
* blocked. The sources will be reset and reloaded, to conserve memory. * blocked. The sources will be reset and reloaded, to conserve memory.
* */ * */
private void onSelect() { private void onSelect() {
if (isCurrentIndexLoaded() && !isBlocked) { if (isBlocked) return;
if (isCurrentIndexLoaded()) {
sync(); sync();
} else { } else {
tryBlock(); tryBlock();
resetSources(); resetSources();
} }
load();
} }
private void sync() { private void sync() {
@ -249,6 +262,7 @@ public class PlaybackManager {
final int currentIndex = playQueue.getIndex(); final int currentIndex = playQueue.getIndex();
final PlayQueueItem currentItem = playQueue.get(currentIndex); final PlayQueueItem currentItem = playQueue.get(currentIndex);
if (currentItem != null) load(currentItem); if (currentItem != null) load(currentItem);
else return;
// The rest are just for seamless playback // The rest are just for seamless playback
final int leftBound = Math.max(0, currentIndex - WINDOW_SIZE); final int leftBound = Math.max(0, currentIndex - WINDOW_SIZE);
@ -270,7 +284,6 @@ public class PlaybackManager {
return; return;
} }
if (disposables.size() > 8) disposables.clear();
disposables.add(d); disposables.add(d);
} }
@ -328,7 +341,7 @@ public class PlaybackManager {
} }
} }
private void swap(final int source, final int target) { private void move(final int source, final int target) {
final int sourceIndex = sourceToQueueIndex.indexOf(source); final int sourceIndex = sourceToQueueIndex.indexOf(source);
final int targetIndex = sourceToQueueIndex.indexOf(target); final int targetIndex = sourceToQueueIndex.indexOf(target);

View File

@ -10,7 +10,7 @@ import org.schabi.newpipe.playlist.events.InitEvent;
import org.schabi.newpipe.playlist.events.PlayQueueMessage; import org.schabi.newpipe.playlist.events.PlayQueueMessage;
import org.schabi.newpipe.playlist.events.RemoveEvent; import org.schabi.newpipe.playlist.events.RemoveEvent;
import org.schabi.newpipe.playlist.events.SelectEvent; import org.schabi.newpipe.playlist.events.SelectEvent;
import org.schabi.newpipe.playlist.events.SwapEvent; import org.schabi.newpipe.playlist.events.MoveEvent;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
@ -28,7 +28,7 @@ public abstract class PlayQueue implements Serializable {
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode()); private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
private final int INDEX_CHANGE_DEBOUNCE = 350; private final int INDEX_CHANGE_DEBOUNCE = 350;
public static final boolean DEBUG = false; public static final boolean DEBUG = true;
private final ArrayList<PlayQueueItem> streams; private final ArrayList<PlayQueueItem> streams;
private final AtomicInteger queueIndex; private final AtomicInteger queueIndex;
@ -178,7 +178,7 @@ public abstract class PlayQueue implements Serializable {
queueIndex.set(newIndex); queueIndex.set(newIndex);
} }
broadcast(new SwapEvent(source, target)); broadcast(new MoveEvent(source, target));
} }
} }

View File

@ -1,16 +1,16 @@
package org.schabi.newpipe.playlist.events; package org.schabi.newpipe.playlist.events;
public class SwapEvent implements PlayQueueMessage { public class MoveEvent implements PlayQueueMessage {
final private int from; final private int from;
final private int to; final private int to;
@Override @Override
public PlayQueueEvent type() { public PlayQueueEvent type() {
return PlayQueueEvent.SWAP; return PlayQueueEvent.MOVE;
} }
public SwapEvent(final int from, final int to) { public MoveEvent(final int from, final int to) {
this.from = from; this.from = from;
this.to = to; this.to = to;
} }

View File

@ -16,6 +16,6 @@ public enum PlayQueueEvent {
REMOVE, REMOVE,
// sent when two streams swap place in the play queue // sent when two streams swap place in the play queue
SWAP MOVE
} }

View File

@ -1,5 +1,7 @@
package org.schabi.newpipe.playlist.events; package org.schabi.newpipe.playlist.events;
public interface PlayQueueMessage { import java.io.Serializable;
public interface PlayQueueMessage extends Serializable {
PlayQueueEvent type(); PlayQueueEvent type();
} }