-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) {
if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
if (on) {
if (basePlayerImpl.isPlaying() && !basePlayerImpl.isProgressLoopRunning.get()) basePlayerImpl.startProgressLoop();
if (basePlayerImpl.isPlaying() && !basePlayerImpl.isProgressLoopRunning()) basePlayerImpl.startProgressLoop();
} 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
*/
private void updateNotification(int drawableId) {
private synchronized void updateNotification(int drawableId) {
if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
if (notBuilder == null) return;
if (drawableId != -1) {
@ -275,19 +275,27 @@ public class BackgroundPlayer extends Service {
}
@Override
public void initThumbnail() {
public void initThumbnail(final String url) {
if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
updateNotification(-1);
super.initThumbnail();
super.initThumbnail(url);
}
@Override
public void onThumbnailReceived(Bitmap thumbnail) {
super.onThumbnailReceived(thumbnail);
if (thumbnail != null) {
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, thumbnail);
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, thumbnail);
videoThumbnail = 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);
}
}
@ -303,7 +311,7 @@ public class BackgroundPlayer extends Service {
FAST_FORWARD_REWIND_AMOUNT = 10000;
}
PROGRESS_LOOP_INTERVAL = 1000;
basePlayerImpl.getPlayer().setVolume(1f);
simpleExoPlayer.setVolume(1f);
}
@Override
@ -382,13 +390,13 @@ public class BackgroundPlayer extends Service {
public void sync(final StreamInfo info, final int sortedStreamsIndex) {
super.sync(info, sortedStreamsIndex);
basePlayerImpl.setVideoTitle(info.name);
basePlayerImpl.setUploaderName(info.uploader_name);
setVideoTitle(info.name);
setUploaderName(info.uploader_name);
notRemoteView.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle());
notRemoteView.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName());
bigNotRemoteView.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle());
bigNotRemoteView.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName());
notRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle());
notRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName());
bigNotRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle());
bigNotRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName());
updateNotification(-1);
}
@ -436,7 +444,7 @@ public class BackgroundPlayer extends Service {
onVideoPlayPause();
break;
case ACTION_OPEN_DETAIL:
onOpenDetail(BackgroundPlayer.this, basePlayerImpl.getVideoUrl(), basePlayerImpl.getVideoTitle());
onOpenDetail(BackgroundPlayer.this, getVideoUrl(), getVideoTitle());
break;
case ACTION_REPEAT:
onRepeatClicked();
@ -483,7 +491,7 @@ public class BackgroundPlayer extends Service {
super.onPaused();
updateNotification(R.drawable.ic_play_arrow_white);
if (isProgressLoopRunning.get()) stopProgressLoop();
if (isProgressLoopRunning()) stopProgressLoop();
releaseWifiAndCpu();
}

View file

@ -30,12 +30,9 @@ import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.google.android.exoplayer2.C;
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.util.Util;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfo;
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.PlaybackManager;
import org.schabi.newpipe.playlist.ExternalPlayQueue;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem;
@ -89,7 +85,19 @@ import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
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
@ -101,7 +109,7 @@ public abstract class BasePlayer implements Player.EventListener,
AudioManager.OnAudioFocusChangeListener, PlaybackListener {
// 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";
protected Context context;
@ -134,7 +142,6 @@ public abstract class BasePlayer implements Player.EventListener,
protected String videoUrl = "";
protected String videoTitle = "";
protected String videoThumbnailUrl = "";
protected long videoStartPos = -1;
protected String uploaderName = "";
/*//////////////////////////////////////////////////////////////////////////
@ -144,8 +151,8 @@ public abstract class BasePlayer implements Player.EventListener,
protected PlaybackManager playbackManager;
protected PlayQueue playQueue;
protected int restoreQueueIndex;
protected long restoreWindowPos;
protected int queueStartPos = 0;
protected long videoStartPos = -1;
/*//////////////////////////////////////////////////////////////////////////
// Player
@ -157,21 +164,19 @@ public abstract class BasePlayer implements Player.EventListener,
protected SimpleExoPlayer simpleExoPlayer;
protected boolean isPrepared = false;
protected MediaSource mediaSource;
protected CacheDataSourceFactory cacheDataSourceFactory;
protected final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
protected final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
protected int PROGRESS_LOOP_INTERVAL = 100;
protected AtomicBoolean isProgressLoopRunning = new AtomicBoolean();
protected Handler progressLoop;
protected Runnable progressUpdate;
protected Disposable progressUpdateReactor;
protected SerialDisposable thumbnailReactor;
//////////////////////////////////////////////////////////////////////////*/
public BasePlayer(Context context) {
this.context = context;
this.progressLoop = new Handler();
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
this.audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
@ -184,6 +189,8 @@ public abstract class BasePlayer implements Player.EventListener,
this.intentFilter = new IntentFilter();
setupBroadcastReceiver(intentFilter);
context.registerReceiver(broadcastReceiver, intentFilter);
this.thumbnailReactor = new SerialDisposable();
}
public void setup() {
@ -223,23 +230,31 @@ public abstract class BasePlayer implements Player.EventListener,
simpleExoPlayer.addListener(this);
}
public void initListeners() {
progressUpdate = new Runnable() {
public void initListeners() {}
protected Disposable getProgressReactor() {
return Observable.interval(PROGRESS_LOOP_INTERVAL, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.filter(new Predicate<Long>() {
@Override
public void run() {
//if(DEBUG) Log.d(TAG, "progressUpdate run() called");
onUpdateProgress((int) simpleExoPlayer.getCurrentPosition(), (int) simpleExoPlayer.getDuration(), simpleExoPlayer.getBufferedPercentage());
if (isProgressLoopRunning.get()) progressLoop.postDelayed(this, PROGRESS_LOOP_INTERVAL);
public boolean test(@NonNull Long aLong) throws Exception {
return isProgressLoopRunning();
}
};
})
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
triggerProgressUpdate();
}
});
}
public void handleIntent(Intent intent) {
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
if (intent == null) return;
restoreQueueIndex = intent.getIntExtra(RESTORE_QUEUE_INDEX, 0);
restoreWindowPos = intent.getLongExtra(START_POSITION, 0);
queueStartPos = intent.getIntExtra(RESTORE_QUEUE_INDEX, 0);
videoStartPos = intent.getLongExtra(START_POSITION, 0);
setPlaybackSpeed(intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed()));
switch (intent.getStringExtra(INTENT_TYPE)) {
@ -254,7 +269,6 @@ public abstract class BasePlayer implements Player.EventListener,
}
}
@SuppressWarnings("unchecked")
public void handleExternalPlaylistIntent(Intent intent) {
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);
}
public void initThumbnail() {
if (DEBUG) Log.d(TAG, "initThumbnail() called");
videoThumbnail = null;
if (videoThumbnailUrl == null || videoThumbnailUrl.isEmpty()) return;
ImageLoader.getInstance().resume();
ImageLoader.getInstance().loadImage(videoThumbnailUrl, new SimpleImageLoadingListener() {
public void initThumbnail(final String url) {
final Callable<Bitmap> bitmapCallable = new Callable<Bitmap>() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
if (simpleExoPlayer == null) return;
if (DEBUG)
Log.d(TAG, "onLoadingComplete() called with: imageUri = [" + imageUri + "], view = [" + view + "], loadedImage = [" + loadedImage + "]");
videoThumbnail = loadedImage;
onThumbnailReceived(loadedImage);
public Bitmap call() throws Exception {
return ImageLoader.getInstance().loadImageSync(url);
}
};
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() {
if (DEBUG) Log.d(TAG, "destroyPlayer() called");
if (simpleExoPlayer != null) {
simpleExoPlayer.stop();
simpleExoPlayer.release();
}
if (progressLoop != null && isProgressLoopRunning.get()) stopProgressLoop();
if (isProgressLoopRunning()) stopProgressLoop();
if (audioManager != null) {
audioManager.abandonAudioFocus(this);
audioManager = null;
@ -320,7 +350,11 @@ public abstract class BasePlayer implements Player.EventListener,
if (DEBUG) Log.d(TAG, "destroy() called");
destroyPlayer();
unregisterBroadcastReceiver();
thumbnailReactor.dispose();
thumbnailReactor = null;
videoThumbnail = null;
simpleExoPlayer = null;
}
@ -469,19 +503,19 @@ public abstract class BasePlayer implements Player.EventListener,
public void onLoading() {
if (DEBUG) Log.d(TAG, "onLoading() called");
if (!isProgressLoopRunning.get()) startProgressLoop();
if (!isProgressLoopRunning()) startProgressLoop();
}
public void onPlaying() {
if (DEBUG) Log.d(TAG, "onPlaying() called");
if (!isProgressLoopRunning.get()) startProgressLoop();
if (!isProgressLoopRunning()) startProgressLoop();
}
public void onBuffering() {
}
public void onPaused() {
if (isProgressLoopRunning.get()) stopProgressLoop();
if (isProgressLoopRunning()) stopProgressLoop();
}
public void onPausedSeek() {
@ -489,7 +523,7 @@ public abstract class BasePlayer implements Player.EventListener,
public void onCompleted() {
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
public void onTimelineChanged(Timeline timeline, Object manifest) {
if (DEBUG) Log.d(TAG, "onTimelineChanged(), timeline size = " + timeline.getWindowCount());
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
Log.w(TAG, "onTracksChanged() called, unsupported operation. Is this expected?");
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
if (DEBUG) Log.d(TAG, "playbackParameters(), speed: " + playbackParameters.speed + ", pitch: " + playbackParameters.pitch);
}
@Override
public void onLoadingChanged(boolean isLoading) {
if (DEBUG) Log.d(TAG, "onLoadingChanged() called with: isLoading = [" + isLoading + "]");
if (!isLoading && getCurrentState() == STATE_PAUSED && isProgressLoopRunning.get()) stopProgressLoop();
else if (isLoading && !isProgressLoopRunning.get()) startProgressLoop();
if (!isLoading && getCurrentState() == STATE_PAUSED && isProgressLoopRunning()) stopProgressLoop();
else if (isLoading && !isProgressLoopRunning()) startProgressLoop();
}
@Override
@ -595,6 +632,11 @@ public abstract class BasePlayer implements Player.EventListener,
playbackManager.refresh(newIndex);
}
@Override
public void onRepeatModeChanged(int i) {
if (DEBUG) Log.d(TAG, "onRepeatModeChanged() called with: mode = [" + i + "]");
}
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
@ -614,13 +656,13 @@ public abstract class BasePlayer implements Player.EventListener,
if (simpleExoPlayer == null) return;
if (DEBUG) Log.d(TAG, "Unblocking...");
if (restoreQueueIndex != playQueue.getIndex()) {
restoreQueueIndex = playQueue.getIndex();
restoreWindowPos = 0;
if (queueStartPos != playQueue.getIndex()) {
queueStartPos = playQueue.getIndex();
videoStartPos = 0;
}
simpleExoPlayer.prepare(playbackManager.getMediaSource());
simpleExoPlayer.seekTo(playbackManager.getCurrentSourceIndex(), restoreWindowPos);
simpleExoPlayer.seekTo(playbackManager.getCurrentSourceIndex(), videoStartPos);
simpleExoPlayer.setPlayWhenReady(false);
}
@ -633,15 +675,16 @@ public abstract class BasePlayer implements Player.EventListener,
videoThumbnailUrl = info.thumbnail_url;
videoTitle = info.name;
initThumbnail();
initThumbnail(videoThumbnailUrl);
if (simpleExoPlayer.getCurrentWindowIndex() != playbackManager.getCurrentSourceIndex()) {
if (DEBUG) Log.w(TAG, "Rewinding to correct window");
if (simpleExoPlayer.getCurrentTimeline().getWindowCount() > playbackManager.getCurrentSourceIndex()) {
simpleExoPlayer.seekToDefaultPosition(playbackManager.getCurrentSourceIndex());
} else {
Toast.makeText(context, "Play Queue out of sync", Toast.LENGTH_SHORT).show();
simpleExoPlayer.seekToDefaultPosition();
if (DEBUG) Log.w(TAG, "Play Queue out of sync");
playbackManager.reset();
return;
}
}
@ -674,26 +717,12 @@ public abstract class BasePlayer implements Player.EventListener,
public void onVideoPlayPause() {
if (DEBUG) Log.d(TAG, "onVideoPlayPause() called");
if (currentState == STATE_COMPLETED) {
onVideoPlayPauseRepeat();
return;
}
if (!isPlaying()) audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
else audioManager.abandonAudioFocus(this);
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() {
if (DEBUG) Log.d(TAG, "onFastRewind() called");
seekBy(-FAST_FORWARD_REWIND_AMOUNT);
@ -704,10 +733,6 @@ public abstract class BasePlayer implements Player.EventListener,
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) {
if (DEBUG) Log.d(TAG, "seekBy() called with: milliSeconds = [" + milliSeconds + "]");
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() {
progressLoop.removeCallbacksAndMessages(null);
isProgressLoopRunning.set(true);
progressLoop.post(progressUpdate);
if (progressUpdateReactor != null) progressUpdateReactor.dispose();
progressUpdateReactor = getProgressReactor();
}
protected void stopProgressLoop() {
isProgressLoopRunning.set(false);
progressLoop.removeCallbacksAndMessages(null);
if (progressUpdateReactor != null) progressUpdateReactor.dispose();
progressUpdateReactor = null;
}
protected void tryDeleteCacheFiles(Context context) {
@ -902,4 +926,8 @@ public abstract class BasePlayer implements Player.EventListener,
public boolean isPlayerReady() {
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();
gestureDetector = new GestureDetector(context, listener);
gestureDetector.setIsLongpressEnabled(false);
playerImpl.getRootView().setOnTouchListener(listener);
getRootView().setOnTouchListener(listener);
repeatButton.setOnClickListener(this);
playPauseButton.setOnClickListener(this);
@ -252,8 +252,10 @@ public class MainVideoPlayer extends Activity {
@Override
public void onFullScreenButtonClicked() {
super.onFullScreenButtonClicked();
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
&& !PermissionHelper.checkSystemAlertWindowPermission(MainVideoPlayer.this)) {
@ -261,11 +263,11 @@ public class MainVideoPlayer extends Activity {
return;
}
context.startService(NavigationHelper.getOpenVideoPlayerIntent(context, PopupVideoPlayer.class, playerImpl));
if (playerImpl != null) playerImpl.destroyPlayer();
context.startService(NavigationHelper.getOpenVideoPlayerIntent(context, PopupVideoPlayer.class, this));
destroyPlayer();
((View) getControlAnimationView().getParent()).setVisibility(View.GONE);
MainVideoPlayer.this.finish();
finish();
}
@Override
@ -302,10 +304,10 @@ public class MainVideoPlayer extends Activity {
if (getCurrentState() != STATE_COMPLETED) {
getControlsVisibilityHandler().removeCallbacksAndMessages(null);
animateView(playerImpl.getControlsRoot(), true, 300, 0, new Runnable() {
animateView(getControlsRoot(), true, 300, 0, new Runnable() {
@Override
public void run() {
if (getCurrentState() == STATE_PLAYING && !playerImpl.isSomePopupMenuVisible()) {
if (getCurrentState() == STATE_PLAYING && !isSomePopupMenuVisible()) {
hideControls(300, DEFAULT_CONTROLS_HIDE_TIME);
}
}
@ -321,7 +323,7 @@ public class MainVideoPlayer extends Activity {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar);
if (playerImpl.wasPlaying()) {
if (wasPlaying()) {
hideControls(100, 0);
}
}
@ -457,11 +459,6 @@ public class MainVideoPlayer extends Activity {
public ImageButton getPlayPauseButton() {
return playPauseButton;
}
@Override
public void onRepeatModeChanged(int i) {
}
}
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.stream.StreamInfo;
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.UserAction;
import org.schabi.newpipe.util.Constants;
@ -115,14 +119,7 @@ public class PopupVideoPlayer extends Service {
private float minimumWidth, minimumHeight;
private float maximumWidth, maximumHeight;
private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha";
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 Disposable currentWorker;
@ -148,7 +145,6 @@ public class PopupVideoPlayer extends Service {
if (playerImpl.getPlayer() == null) initPopup();
if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true);
if (imageLoader != null) imageLoader.clearMemoryCache();
if (intent.getStringExtra(Constants.KEY_URL) != null) {
final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
final String url = intent.getStringExtra(Constants.KEY_URL);
@ -245,61 +241,6 @@ public class PopupVideoPlayer extends Service {
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
//////////////////////////////////////////////////////////////////////////*/
@ -400,25 +341,22 @@ public class PopupVideoPlayer extends Service {
@Override
public void destroy() {
super.destroy();
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, null);
}
@Override
public void onThumbnailReceived(Bitmap thumbnail) {
super.onThumbnailReceived(thumbnail);
if (thumbnail != null) {
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, thumbnail);
updateNotification(-1);
}
}
@Override
public void onFullScreenButtonClicked() {
super.onFullScreenButtonClicked();
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
Intent intent;
if (!getSharedPreferences().getBoolean(getResources().getString(R.string.use_old_player_key), false)) {
intent = NavigationHelper.getOpenVideoPlayerIntent(context, MainVideoPlayer.class, playerImpl);
if (!playerImpl.isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false);
intent = NavigationHelper.getOpenVideoPlayerIntent(context, MainVideoPlayer.class, this);
if (!isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else {
intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class)
@ -429,31 +367,10 @@ public class PopupVideoPlayer extends Service {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
if (playerImpl != null) playerImpl.destroyPlayer();
destroyPlayer();
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
public void onDismiss(PopupMenu menu) {
super.onDismiss(menu);
@ -469,7 +386,7 @@ public class PopupVideoPlayer extends Service {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar);
if (playerImpl.wasPlaying()) {
if (wasPlaying()) {
hideControls(100, 0);
}
}
@ -507,13 +424,13 @@ public class PopupVideoPlayer extends Service {
onVideoClose();
break;
case ACTION_PLAY_PAUSE:
playerImpl.onVideoPlayPause();
onVideoPlayPause();
break;
case ACTION_OPEN_DETAIL:
onOpenDetail(PopupVideoPlayer.this, playerImpl.getVideoUrl(), playerImpl.getVideoTitle());
onOpenDetail(PopupVideoPlayer.this, getVideoUrl(), getVideoTitle());
break;
case ACTION_REPEAT:
playerImpl.onRepeatClicked();
onRepeatClicked();
break;
}
}
@ -524,38 +441,32 @@ public class PopupVideoPlayer extends Service {
@Override
public void onLoading() {
super.onLoading();
updateNotification(R.drawable.ic_play_arrow_white);
}
@Override
public void onPlaying() {
super.onPlaying();
updateNotification(R.drawable.ic_pause_white);
}
@Override
public void onBuffering() {
super.onBuffering();
updateNotification(R.drawable.ic_play_arrow_white);
}
@Override
public void onPaused() {
super.onPaused();
updateNotification(R.drawable.ic_play_arrow_white);
showAndAnimateControl(R.drawable.ic_play_arrow_white, false);
}
@Override
public void onPausedSeek() {
super.onPausedSeek();
updateNotification(R.drawable.ic_play_arrow_white);
}
@Override
public void onCompleted() {
super.onCompleted();
updateNotification(R.drawable.ic_replay_white);
showAndAnimateControl(R.drawable.ic_replay_white, false);
}
@ -564,10 +475,6 @@ public class PopupVideoPlayer extends Service {
public TextView getResizingIndicator() {
return resizingIndicator;
}
@Override
public void onRepeatModeChanged(int i) {
}
}
private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
@ -746,49 +653,16 @@ public class PopupVideoPlayer extends Service {
this.serviceId = serviceId;
}
public void onReceive(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);
}
public void onReceive(final StreamInfo info) {
if (info.start_position > 0) playerImpl.setVideoStartPos(info.start_position * 1000);
else playerImpl.setVideoStartPos(-1);
mainHandler.post(new Runnable() {
@Override
public void run() {
playerImpl.playQueue = new SinglePlayQueue(info, PlayQueueItem.DEFAULT_QUALITY);
playerImpl.playQueue.init();
}
});
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);
}
});
playerImpl.playbackManager = new PlaybackManager(playerImpl, playerImpl.playQueue);
}
});
}

View file

@ -309,7 +309,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
public void onLoading() {
if (DEBUG) Log.d(TAG, "onLoading() called");
if (!isProgressLoopRunning.get()) startProgressLoop();
if (!isProgressLoopRunning()) startProgressLoop();
controlsVisibilityHandler.removeCallbacksAndMessages(null);
animateView(controlsRoot, false, 300);
@ -331,7 +331,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
@Override
public void onPlaying() {
if (DEBUG) Log.d(TAG, "onPlaying() called");
if (!isProgressLoopRunning.get()) startProgressLoop();
if (!isProgressLoopRunning()) startProgressLoop();
showAndAnimateControl(-1, true);
loadingPanel.setVisibility(View.GONE);
showControlsThenHide();
@ -362,7 +362,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
public void onCompleted() {
if (DEBUG) Log.d(TAG, "onCompleted() called");
if (isProgressLoopRunning.get()) stopProgressLoop();
if (isProgressLoopRunning()) stopProgressLoop();
showControls(500);
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
public void onThumbnailReceived(Bitmap thumbnail) {
super.onThumbnailReceived(thumbnail);
if (thumbnail != null) endScreen.setImageBitmap(thumbnail);
}
protected abstract void onFullScreenButtonClicked();
protected void onFullScreenButtonClicked() {
if (!isPlayerReady()) return;
}
@Override
public void onFastRewind() {
@ -501,8 +494,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (qualityPopupMenuGroupId == menuItem.getGroupId()) {
if (selectedIndexStream == menuItem.getItemId()) return true;
restoreQueueIndex = playQueue.getIndex();
restoreWindowPos = simpleExoPlayer.getCurrentPosition();
queueStartPos = playQueue.getIndex();
videoStartPos = simpleExoPlayer.getCurrentPosition();
playbackManager.updateCurrent(menuItem.getItemId());
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);
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.events.PlayQueueMessage;
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.Collections;
@ -91,7 +91,9 @@ public class PlaybackManager {
public void report(final Exception error) {
// 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();
playQueue.remove(index);
@ -101,7 +103,7 @@ public class PlaybackManager {
}
public void updateCurrent(final int newSortedStreamsIndex) {
if (!tryBlock()) return;
tryBlock();
PlayQueueItem item = playQueue.getCurrent();
item.setSortedQualityIndex(newSortedStreamsIndex);
@ -110,6 +112,13 @@ public class PlaybackManager {
load();
}
public void reset() {
tryBlock();
resetSources();
load();
}
public void dispose() {
if (playQueueReactor != null) playQueueReactor.cancel();
if (disposables != null) disposables.dispose();
@ -143,8 +152,8 @@ public class PlaybackManager {
switch (event.type()) {
case INIT:
isBlocked = true;
break;
case APPEND:
load();
break;
case SELECT:
onSelect();
@ -153,10 +162,9 @@ public class PlaybackManager {
final RemoveEvent removeEvent = (RemoveEvent) event;
remove(removeEvent.index());
break;
case SWAP:
final SwapEvent swapEvent = (SwapEvent) event;
swap(swapEvent.getFrom(), swapEvent.getTo());
load();
case MOVE:
final MoveEvent moveEvent = (MoveEvent) event;
move(moveEvent.getFrom(), moveEvent.getTo());
break;
default:
break;
@ -167,6 +175,8 @@ public class PlaybackManager {
playQueue.fetch();
} else if (playQueue.isEmpty()) {
playbackListener.shutdown();
} else {
load(); // All event warrants a load
}
if (playQueueReactor != null) playQueueReactor.request(1);
@ -176,9 +186,7 @@ public class PlaybackManager {
public void onError(@NonNull Throwable e) {}
@Override
public void onComplete() {
dispose();
}
public void onComplete() {}
};
}
@ -214,21 +222,26 @@ public class PlaybackManager {
/*
* 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.
*
* 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.
* */
private void onSelect() {
if (isCurrentIndexLoaded() && !isBlocked) {
if (isBlocked) return;
if (isCurrentIndexLoaded()) {
sync();
} else {
tryBlock();
resetSources();
}
load();
}
private void sync() {
@ -249,6 +262,7 @@ public class PlaybackManager {
final int currentIndex = playQueue.getIndex();
final PlayQueueItem currentItem = playQueue.get(currentIndex);
if (currentItem != null) load(currentItem);
else return;
// The rest are just for seamless playback
final int leftBound = Math.max(0, currentIndex - WINDOW_SIZE);
@ -270,7 +284,6 @@ public class PlaybackManager {
return;
}
if (disposables.size() > 8) disposables.clear();
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 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.RemoveEvent;
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.util.ArrayList;
@ -28,7 +28,7 @@ public abstract class PlayQueue implements Serializable {
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
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 AtomicInteger queueIndex;
@ -178,7 +178,7 @@ public abstract class PlayQueue implements Serializable {
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;
public class SwapEvent implements PlayQueueMessage {
public class MoveEvent implements PlayQueueMessage {
final private int from;
final private int to;
@Override
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.to = to;
}

View file

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

View file

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