From 0b1eda30503b05753dcd5380bffffe4637c96d6b Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Sun, 29 Oct 2017 12:39:53 -0700 Subject: [PATCH 01/13] -Improved null checks in player stream resolution. -Improved naming in MediaSourceManager --- .../newpipe/player/BackgroundPlayer.java | 3 ++- .../schabi/newpipe/player/VideoPlayer.java | 11 +++++---- .../player/playback/MediaSourceManager.java | 24 +++++++++++-------- .../player/playback/PlaybackListener.java | 1 + 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 863eaf3e8..25735330d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -380,9 +380,10 @@ public final class BackgroundPlayer extends Service { } @Override + @Nullable public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { final int index = ListHelper.getDefaultAudioFormat(context, info.audio_streams); - if (index < 0) return null; + if (index < 0 || index >= info.audio_streams.size()) return null; final AudioStream audio = info.audio_streams.get(index); return buildMediaSource(audio.url, MediaFormat.getSuffixById(audio.format)); diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index 1a386d45d..07ead4f0e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -272,17 +272,18 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. } @Override + @Nullable public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { final List videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false); - final VideoStream video; + final int index; if (playbackQuality == null) { - final int index = getDefaultResolutionIndex(videos); - video = videos.get(index); + index = getDefaultResolutionIndex(videos); } else { - final int index = getOverrideResolutionIndex(videos, getPlaybackQuality()); - video = videos.get(index); + index = getOverrideResolutionIndex(videos, getPlaybackQuality()); } + if (index < 0 || index >= videos.size()) return null; + final VideoStream video = videos.get(index); final MediaSource streamSource = buildMediaSource(video.url, MediaFormat.getSuffixById(video.format)); final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams); diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index 8c9ff1440..f7d267c50 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -29,7 +29,7 @@ import io.reactivex.subjects.PublishSubject; public class MediaSourceManager { private final String TAG = "MediaSourceManager@" + Integer.toHexString(hashCode()); // One-side rolling window size for default loading - // Effectively loads windowSize * 2 + 1 streams, must be greater than 0 + // Effectively loads windowSize * 2 + 1 streams per call to load, must be greater than 0 private final int windowSize; private final PlaybackListener playbackListener; private final PlayQueue playQueue; @@ -38,7 +38,7 @@ public class MediaSourceManager { // The higher it is, the less loading occurs during rapid noncritical timeline changes // Not recommended to go below 100ms private final long loadDebounceMillis; - private final PublishSubject loadSignal; + private final PublishSubject debouncedLoadSignal; private final Disposable debouncedLoader; private final DeferredMediaSource.Callback sourceBuilder; @@ -69,7 +69,7 @@ public class MediaSourceManager { this.loadDebounceMillis = loadDebounceMillis; this.syncReactor = new SerialDisposable(); - this.loadSignal = PublishSubject.create(); + this.debouncedLoadSignal = PublishSubject.create(); this.debouncedLoader = getDebouncedLoader(); this.sourceBuilder = getSourceBuilder(); @@ -101,7 +101,7 @@ public class MediaSourceManager { * Dispose the manager and releases all message buses and loaders. * */ public void dispose() { - if (loadSignal != null) loadSignal.onComplete(); + if (debouncedLoadSignal != null) debouncedLoadSignal.onComplete(); if (debouncedLoader != null) debouncedLoader.dispose(); if (playQueueReactor != null) playQueueReactor.cancel(); if (syncReactor != null) syncReactor.dispose(); @@ -118,7 +118,7 @@ public class MediaSourceManager { * Unblocks the player once the item at the current index is loaded. * */ public void load() { - loadSignal.onNext(System.currentTimeMillis()); + loadDebounced(); } /** @@ -195,14 +195,14 @@ public class MediaSourceManager { case REORDER: case ERROR: case APPEND: - loadInternal(); // low frequency, critical events + loadImmediate(); // low frequency, critical events break; case REMOVE: case SELECT: case MOVE: case RECOVERY: default: - load(); // high frequency or noncritical events + loadDebounced(); // high frequency or noncritical events break; } @@ -262,7 +262,11 @@ public class MediaSourceManager { syncReactor.set(currentItem.getStream().subscribe(syncPlayback, onError)); } - private void loadInternal() { + private void loadDebounced() { + debouncedLoadSignal.onNext(System.currentTimeMillis()); + } + + private void loadImmediate() { // The current item has higher priority final int currentIndex = playQueue.getIndex(); final PlayQueueItem currentItem = playQueue.getItem(currentIndex); @@ -307,13 +311,13 @@ public class MediaSourceManager { } private Disposable getDebouncedLoader() { - return loadSignal + return debouncedLoadSignal .debounce(loadDebounceMillis, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer() { @Override public void accept(Long timestamp) throws Exception { - loadInternal(); + loadImmediate(); } }); } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java index 226c643d5..dfed04c01 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java @@ -43,6 +43,7 @@ public interface PlaybackListener { * * May be called at any time. * */ + @Nullable MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info); /** From 01e031e7e79dcc9aa7c0b68b50d5b976c1b30e1c Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 31 Oct 2017 12:42:56 -0700 Subject: [PATCH 02/13] -Modified recovery to not set if progress position is 0 or less. -Modified queue item synchronization to no longer trigger update when the sync is run on the identical item. --- .../org/schabi/newpipe/player/BasePlayer.java | 29 +++++++++++-------- .../player/playback/MediaSourceManager.java | 17 +++++------ 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 8508bb237..748ef00c5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -26,6 +26,7 @@ import android.content.IntentFilter; import android.graphics.Bitmap; import android.media.AudioManager; import android.net.Uri; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; @@ -76,7 +77,6 @@ import java.util.concurrent.TimeUnit; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.annotations.NonNull; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import io.reactivex.functions.Predicate; @@ -193,7 +193,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen .observeOn(AndroidSchedulers.mainThread()) .filter(new Predicate() { @Override - public boolean test(@NonNull Long aLong) throws Exception { + public boolean test(Long aLong) throws Exception { return isProgressLoopRunning(); } }) @@ -235,7 +235,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen initPlayback(queue); } - protected void initPlayback(@NonNull final PlayQueue queue) { + protected void initPlayback(final PlayQueue queue) { playQueue = queue; playQueue.init(); playbackManager = new MediaSourceManager(this, playQueue); @@ -514,11 +514,10 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen } break; case Player.STATE_READY: //3 - recover(); - if (!isPrepared) { isPrepared = true; onPrepared(playWhenReady); + recover(); break; } if (currentState == STATE_PAUSED_SEEK) break; @@ -631,17 +630,21 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen } @Override - public void sync(@android.support.annotation.NonNull final PlayQueueItem item, + public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) { - if (simpleExoPlayer == null) return; - if (DEBUG) Log.d(TAG, "Syncing..."); - + if (currentItem == item && currentInfo == info) return; currentItem = item; currentInfo = info; + if (DEBUG) Log.d(TAG, "Syncing..."); + if (simpleExoPlayer == null) return; + // Check if on wrong window - final int currentSourceIndex = playQueue.getIndex(); - if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex) { + final int currentSourceIndex = playQueue.indexOf(item); + if (currentSourceIndex != playQueue.getIndex()) { + throw new IllegalStateException("Play Queue may be desynchronized: item index=[" + + currentSourceIndex + "], queue index=[" + playQueue.getIndex() + "]"); + } else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex) { final long startPos = info != null ? info.start_position : 0; if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos)); simpleExoPlayer.seekTo(currentSourceIndex, startPos); @@ -900,7 +903,9 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen final int queuePos = playQueue.getIndex(); final long windowPos = simpleExoPlayer.getCurrentPosition(); - setRecovery(queuePos, windowPos); + if (windowPos > 0) { + setRecovery(queuePos, windowPos); + } } public void setRecovery(final int queuePos, final long windowPos) { diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index f7d267c50..2e4e4af5c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -52,7 +52,7 @@ public class MediaSourceManager { public MediaSourceManager(@NonNull final PlaybackListener listener, @NonNull final PlayQueue playQueue) { - this(listener, playQueue, 1, 1000L); + this(listener, playQueue, 1, 400L); } private MediaSourceManager(@NonNull final PlaybackListener listener, @@ -162,7 +162,7 @@ public class MediaSourceManager { return; } - // why no pattern matching in Java =( + // Event specific action switch (event.type()) { case INIT: case REORDER: @@ -172,31 +172,28 @@ public class MediaSourceManager { case APPEND: populateSources(); break; - case SELECT: - sync(); - break; case REMOVE: final RemoveEvent removeEvent = (RemoveEvent) event; remove(removeEvent.getRemoveIndex()); - // Sync only when the currently playing is removed - if (removeEvent.getQueueIndex() == removeEvent.getRemoveIndex()) sync(); break; case MOVE: final MoveEvent moveEvent = (MoveEvent) event; move(moveEvent.getFromIndex(), moveEvent.getToIndex()); break; + case SELECT: case RECOVERY: default: break; } + // Loading and Syncing switch (event.type()) { case INIT: case REORDER: case ERROR: - case APPEND: loadImmediate(); // low frequency, critical events break; + case APPEND: case REMOVE: case SELECT: case MOVE: @@ -294,7 +291,9 @@ public class MediaSourceManager { final DeferredMediaSource mediaSource = (DeferredMediaSource) sources.getMediaSource(playQueue.indexOf(item)); if (mediaSource.state() == DeferredMediaSource.STATE_PREPARED) mediaSource.load(); - if (tryUnblock()) sync(); + + tryUnblock(); + if (!isBlocked) sync(); } private void resetSources() { From 38b2ffd4504cefaa79885d2f6b90a9c129645445 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 31 Oct 2017 17:07:12 -0700 Subject: [PATCH 03/13] -Added fling to toss popup view when velocity is below shutdown. -Added string for unknown content. -Fixed NPE when UI element is touched after player shuts down in service activity. -Fixed shuffle reset caused by position discontinuity offsets index. -Moved some more player shared preferences to PlayerHelper. --- .../org/schabi/newpipe/player/BasePlayer.java | 24 ++++++++++------- .../newpipe/player/PopupVideoPlayer.java | 26 ++++++++++++++----- .../newpipe/player/ServicePlayerActivity.java | 24 +++++++++++------ .../newpipe/player/helper/PlayerHelper.java | 19 ++++++++++++++ app/src/main/res/values/strings.xml | 2 ++ 5 files changed, 71 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 748ef00c5..c24a46049 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -590,12 +590,12 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with window index = [" + newWindowIndex + "]"); // If the user selects a new track, then the discontinuity occurs after the index is changed. - // Therefore, the only source that causes a discrepancy would be autoplay, + // Therefore, the only source that causes a discrepancy would be gapless transition, // which can only offset the current track by +1. - if (newWindowIndex != playQueue.getIndex() && playbackManager != null) { + if (newWindowIndex == playQueue.getIndex() + 1) { playQueue.offsetIndex(+1); - playbackManager.load(); } + playbackManager.load(); } @Override @@ -612,6 +612,8 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen if (simpleExoPlayer == null) return; if (DEBUG) Log.d(TAG, "Blocking..."); + currentItem = null; + currentInfo = null; simpleExoPlayer.stop(); isPrepared = false; @@ -642,8 +644,8 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen // Check if on wrong window final int currentSourceIndex = playQueue.indexOf(item); if (currentSourceIndex != playQueue.getIndex()) { - throw new IllegalStateException("Play Queue may be desynchronized: item index=[" + - currentSourceIndex + "], queue index=[" + playQueue.getIndex() + "]"); + Log.e(TAG, "Play Queue may be desynchronized: item index=[" + currentSourceIndex + + "], queue index=[" + playQueue.getIndex() + "]"); } else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex) { final long startPos = info != null ? info.start_position : 0; if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos)); @@ -829,15 +831,15 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen } public String getVideoUrl() { - return currentItem == null ? null : currentItem.getUrl(); + return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUrl(); } public String getVideoTitle() { - return currentItem == null ? null : currentItem.getTitle(); + return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getTitle(); } public String getUploaderName() { - return currentItem == null ? null : currentItem.getUploader(); + return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader(); } public boolean isCompleted() { @@ -873,8 +875,10 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen } public PlaybackParameters getPlaybackParameters() { + final PlaybackParameters defaultParameters = new PlaybackParameters(1f, 1f); + if (simpleExoPlayer == null) return defaultParameters; final PlaybackParameters parameters = simpleExoPlayer.getPlaybackParameters(); - return parameters == null ? new PlaybackParameters(1f, 1f) : parameters; + return parameters == null ? defaultParameters : parameters; } public void setPlaybackParameters(float speed, float pitch) { @@ -903,7 +907,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen final int queuePos = playQueue.getIndex(); final long windowPos = simpleExoPlayer.getCurrentPosition(); - if (windowPos > 0) { + if (windowPos > 0 && windowPos <= simpleExoPlayer.getDuration()) { setRecovery(queuePos, windowPos); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 89d58141d..71068dbea 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -65,6 +65,7 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.player.event.PlayerEventListener; +import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.old.PlayVideoActivity; import org.schabi.newpipe.player.helper.LockManager; import org.schabi.newpipe.playlist.PlayQueueItem; @@ -96,7 +97,6 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView; public final class PopupVideoPlayer extends Service { private static final String TAG = ".PopupVideoPlayer"; private static final boolean DEBUG = BasePlayer.DEBUG; - private static final int SHUTDOWN_FLING_VELOCITY = 10000; private static final int NOTIFICATION_ID = 40028922; public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE"; @@ -112,6 +112,9 @@ public final class PopupVideoPlayer extends Service { private WindowManager.LayoutParams windowLayoutParams; private GestureDetector gestureDetector; + private int shutdownFlingVelocity; + private int tossFlingVelocity; + private float screenWidth, screenHeight; private float popupWidth, popupHeight; @@ -211,12 +214,14 @@ public final class PopupVideoPlayer extends Service { View rootView = View.inflate(this, R.layout.player_popup, null); playerImpl.setup(rootView); + shutdownFlingVelocity = PlayerHelper.getShutdownFlingVelocity(this); + tossFlingVelocity = PlayerHelper.getTossFlingVelocity(this); + updateScreenSize(); + final boolean popupRememberSizeAndPos = PlayerHelper.isRememberingPopupDimensions(this); + final float defaultSize = getResources().getDimension(R.dimen.popup_default_width); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - boolean popupRememberSizeAndPos = sharedPreferences.getBoolean(getString(R.string.popup_remember_size_pos_key), true); - - float defaultSize = getResources().getDimension(R.dimen.popup_default_width); popupWidth = popupRememberSizeAndPos ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize; final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_PHONE : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -791,11 +796,20 @@ public final class PopupVideoPlayer extends Service { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (DEBUG) Log.d(TAG, "Fling velocity: dX=[" + velocityX + "], dY=[" + velocityY + "]"); if (playerImpl == null) return false; - if (Math.abs(velocityX) > SHUTDOWN_FLING_VELOCITY) { - if (DEBUG) Log.d(TAG, "Popup close fling velocity= " + velocityX); + + final float absVelocityX = Math.abs(velocityX); + final float absVelocityY = Math.abs(velocityY); + if (absVelocityX > shutdownFlingVelocity) { onClose(); return true; + } else if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) { + if (absVelocityX > tossFlingVelocity) windowLayoutParams.x = (int) velocityX; + if (absVelocityY > tossFlingVelocity) windowLayoutParams.y = (int) velocityY; + checkPositionBounds(); + windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); + return true; } return false; } diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index ef77cdda2..4fe228dfc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -286,6 +286,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem menuItem) { + if (player == null) return false; + player.setPlaybackSpeed(playbackSpeed); return true; } @@ -304,6 +306,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem menuItem) { + if (player == null) return false; + player.setPlaybackPitch(playbackPitch); return true; } @@ -317,6 +321,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity remove.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem menuItem) { + if (player == null) return false; + final int index = player.getPlayQueue().indexOf(item); if (index != -1) player.getPlayQueue().remove(index); return true; @@ -349,7 +355,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity final int sourceIndex = source.getLayoutPosition(); final int targetIndex = target.getLayoutPosition(); - player.getPlayQueue().move(sourceIndex, targetIndex); + if (player != null) player.getPlayQueue().move(sourceIndex, targetIndex); return true; } @@ -372,11 +378,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity return new PlayQueueItemBuilder.OnSelectedListener() { @Override public void selected(PlayQueueItem item, View view) { - player.onSelected(item); + if (player != null) player.onSelected(item); } @Override public void held(PlayQueueItem item, View view) { + if (player == null) return; + final int index = player.getPlayQueue().indexOf(item); if (index != -1) buildItemPopupMenu(item, view); } @@ -403,19 +411,19 @@ public abstract class ServicePlayerActivity extends AppCompatActivity @Override public void onClick(View view) { if (view.getId() == repeatButton.getId()) { - player.onRepeatClicked(); + if (player != null) player.onRepeatClicked(); } else if (view.getId() == backwardButton.getId()) { - player.onPlayPrevious(); + if (player != null) player.onPlayPrevious(); } else if (view.getId() == playPauseButton.getId()) { - player.onVideoPlayPause(); + if (player != null) player.onVideoPlayPause(); } else if (view.getId() == forwardButton.getId()) { - player.onPlayNext(); + if (player != null) player.onPlayNext(); } else if (view.getId() == shuffleButton.getId()) { - player.onShuffleClicked(); + if (player != null) player.onShuffleClicked(); } else if (view.getId() == playbackSpeedButton.getId()) { playbackSpeedPopupMenu.show(); @@ -450,7 +458,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity @Override public void onStopTrackingTouch(SeekBar seekBar) { - player.simpleExoPlayer.seekTo(seekBar.getProgress()); + if (player != null) player.simpleExoPlayer.seekTo(seekBar.getProgress()); seekDisplay.setVisibility(View.GONE); seeking = false; } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 558f82b43..40063ba40 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -12,6 +12,8 @@ import java.text.NumberFormat; import java.util.Formatter; import java.util.Locale; +import javax.annotation.Nonnull; + public class PlayerHelper { private PlayerHelper() {} @@ -56,6 +58,10 @@ public class PlayerHelper { return isUsingOldPlayer(context, false); } + public static boolean isRememberingPopupDimensions(@Nonnull final Context context) { + return isRememberingPopupDimensions(context, true); + } + public static long getPreferredCacheSize(@NonNull final Context context) { return 64 * 1024 * 1024L; } @@ -83,6 +89,15 @@ public class PlayerHelper { public static boolean isUsingDSP(@NonNull final Context context) { return true; } + + public static int getShutdownFlingVelocity(@Nonnull final Context context) { + return 10000; + } + + public static int getTossFlingVelocity(@Nonnull final Context context) { + return 2500; + } + //////////////////////////////////////////////////////////////////////////// // Private helpers //////////////////////////////////////////////////////////////////////////// @@ -103,4 +118,8 @@ public class PlayerHelper { private static boolean isUsingOldPlayer(@NonNull final Context context, final boolean b) { return getPreferences(context).getBoolean(context.getString(R.string.use_old_player_key), b); } + + private static boolean isRememberingPopupDimensions(@Nonnull final Context context, final boolean b) { + return getPreferences(context).getBoolean(context.getString(R.string.popup_remember_size_pos_key), b); + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ad69915b9..7092feaf5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -122,6 +122,8 @@ NewPipe Notification Notifications for NewPipe Background and Popup Players + [Unknown] + Error Network error From 87febf8679e628bb93e30f96784d487fa7df14ae Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Wed, 1 Nov 2017 22:33:13 -0700 Subject: [PATCH 04/13] -Added dropdown to start play for all StreamInfoItem. -Refactored NavigationHelper to allow service player control to open anywhere. -Refactored NavigationHelper to allow starting player at anywhere. --- .../fragments/detail/VideoDetailFragment.java | 30 +++--- .../fragments/list/BaseListFragment.java | 10 ++ .../list/playlist/PlaylistFragment.java | 11 +-- .../subscription/SubscriptionFragment.java | 4 + .../newpipe/info_list/InfoItemBuilder.java | 2 + .../holder/StreamMiniInfoItemHolder.java | 91 ++++++++++++++++++ .../newpipe/player/BackgroundPlayer.java | 13 +-- .../newpipe/player/PopupVideoPlayer.java | 11 +-- .../newpipe/playlist/SinglePlayQueue.java | 11 ++- .../schabi/newpipe/util/NavigationHelper.java | 48 ++++++++- .../drawable-hdpi/ic_more_vert_black_24dp.png | Bin 0 -> 132 bytes .../drawable-hdpi/ic_more_vert_white_24dp.png | Bin 0 -> 134 bytes .../drawable-mdpi/ic_more_vert_black_24dp.png | Bin 0 -> 108 bytes .../drawable-mdpi/ic_more_vert_white_24dp.png | Bin 0 -> 112 bytes .../ic_more_vert_black_24dp.png | Bin 0 -> 155 bytes .../ic_more_vert_white_24dp.png | Bin 0 -> 158 bytes .../ic_more_vert_black_24dp.png | Bin 0 -> 205 bytes .../ic_more_vert_white_24dp.png | Bin 0 -> 216 bytes .../ic_more_vert_black_24dp.png | Bin 0 -> 272 bytes .../ic_more_vert_white_24dp.png | Bin 0 -> 305 bytes app/src/main/res/layout/list_stream_item.xml | 29 +++++- .../main/res/layout/list_stream_mini_item.xml | 23 ++++- app/src/main/res/values/attrs.xml | 1 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/values/styles.xml | 2 + 25 files changed, 240 insertions(+), 48 deletions(-) create mode 100644 app/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png create mode 100644 app/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_more_vert_black_24dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_more_vert_white_24dp.png diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 63b7e9ace..265e22319 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -36,6 +36,7 @@ import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.PopupMenu; import android.widget.RelativeLayout; import android.widget.Spinner; import android.widget.TextView; @@ -459,6 +460,9 @@ public class VideoDetailFragment extends BaseStateFragment implement public void selected(StreamInfoItem selectedItem) { selectAndLoadVideo(selectedItem.service_id, selectedItem.url, selectedItem.name); } + + @Override + public void dropdownClicked(StreamInfoItem selectedItem, PopupMenu menu) {} }); videoTitleRoot.setOnClickListener(this); @@ -792,16 +796,12 @@ public class VideoDetailFragment extends BaseStateFragment implement ((HistoryListener) activity).onVideoPlayed(currentInfo, getSelectedVideoStream()); } - final PlayQueue playQueue = new SinglePlayQueue(currentInfo); - final Intent intent; + final PlayQueue itemQueue = new SinglePlayQueue(currentInfo); if (append) { - Toast.makeText(activity, R.string.popup_playing_append, Toast.LENGTH_SHORT).show(); - intent = NavigationHelper.getPlayerEnqueueIntent(activity, PopupVideoPlayer.class, playQueue); + NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue); } else { - Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); - intent = NavigationHelper.getPlayerIntent(activity, PopupVideoPlayer.class, playQueue, getSelectedVideoStream().resolution); + NavigationHelper.playOnPopupPlayer(activity, itemQueue); } - activity.startService(intent); } private void openVideoPlayer() { @@ -820,13 +820,11 @@ public class VideoDetailFragment extends BaseStateFragment implement private void openNormalBackgroundPlayer(final boolean append) { - final PlayQueue playQueue = new SinglePlayQueue(currentInfo); + final PlayQueue itemQueue = new SinglePlayQueue(currentInfo); if (append) { - activity.startService(NavigationHelper.getPlayerEnqueueIntent(activity, BackgroundPlayer.class, playQueue)); - Toast.makeText(activity, R.string.background_player_append, Toast.LENGTH_SHORT).show(); + NavigationHelper.enqueueOnBackgroundPlayer(activity, itemQueue); } else { - activity.startService(NavigationHelper.getPlayerIntent(activity, BackgroundPlayer.class, playQueue)); - Toast.makeText(activity, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show(); + NavigationHelper.playOnBackgroundPlayer(activity, itemQueue); } } @@ -865,22 +863,20 @@ public class VideoDetailFragment extends BaseStateFragment implement } private void openNormalPlayer(VideoStream selectedVideoStream) { - Intent mIntent; boolean useOldPlayer = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(getString(R.string.use_old_player_key), false) || (Build.VERSION.SDK_INT < 16); if (!useOldPlayer) { // ExoPlayer - final PlayQueue playQueue = new SinglePlayQueue(currentInfo); - mIntent = NavigationHelper.getPlayerIntent(activity, MainVideoPlayer.class, playQueue, getSelectedVideoStream().resolution); + NavigationHelper.playOnMainPlayer(activity, new SinglePlayQueue(currentInfo)); } else { // Internal Player - mIntent = new Intent(activity, PlayVideoActivity.class) + final Intent mIntent = new Intent(activity, PlayVideoActivity.class) .putExtra(PlayVideoActivity.VIDEO_TITLE, currentInfo.name) .putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.url) .putExtra(PlayVideoActivity.VIDEO_URL, currentInfo.url) .putExtra(PlayVideoActivity.START_POSITION, currentInfo.start_position); + startActivity(mIntent); } - startActivity(mIntent); } private void openExternalVideoPlayer(VideoStream selectedVideoStream) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index 35f6a08d3..e3a94a5e2 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -10,6 +10,7 @@ import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.View; +import android.widget.PopupMenu; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; @@ -139,6 +140,9 @@ public abstract class BaseListFragment extends BaseStateFragment implem useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(), selectedItem.service_id, selectedItem.url, selectedItem.name); } + + @Override + public void dropdownClicked(StreamInfoItem selectedItem, PopupMenu menu) {} }); infoListAdapter.setOnChannelSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() { @@ -149,6 +153,9 @@ public abstract class BaseListFragment extends BaseStateFragment implem useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(), selectedItem.service_id, selectedItem.url, selectedItem.name); } + + @Override + public void dropdownClicked(ChannelInfoItem selectedItem, PopupMenu menu) {} }); infoListAdapter.setOnPlaylistSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() { @@ -159,6 +166,9 @@ public abstract class BaseListFragment extends BaseStateFragment implem useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(), selectedItem.service_id, selectedItem.url, selectedItem.name); } + + @Override + public void dropdownClicked(PlaylistInfoItem selectedItem, PopupMenu menu) {} }); itemsList.clearOnScrollListeners(); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index b88d54524..9d57617d3 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -160,7 +160,7 @@ public class PlaylistFragment extends BaseListInfoFragment { headerPlayAllButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - startActivity(buildPlaylistIntent(MainVideoPlayer.class)); + NavigationHelper.playOnMainPlayer(activity, getPlayQueue()); } }); headerPopupButton.setOnClickListener(new View.OnClickListener() { @@ -173,26 +173,25 @@ public class PlaylistFragment extends BaseListInfoFragment { toast.show(); return; } - activity.startService(buildPlaylistIntent(PopupVideoPlayer.class)); + NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()); } }); headerBackgroundButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - activity.startService(buildPlaylistIntent(BackgroundPlayer.class)); + NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()); } }); } - private Intent buildPlaylistIntent(final Class targetClazz) { - final PlayQueue playQueue = new ExternalPlayQueue( + private PlayQueue getPlayQueue() { + return new ExternalPlayQueue( currentInfo.service_id, currentInfo.url, currentInfo.next_streams_url, infoListAdapter.getItemsList(), 0 ); - return NavigationHelper.getPlayerIntent(activity, targetClazz, playQueue); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/fragments/subscription/SubscriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/subscription/SubscriptionFragment.java index 520586663..c3afa9620 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/subscription/SubscriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/subscription/SubscriptionFragment.java @@ -10,6 +10,7 @@ import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.PopupMenu; import org.schabi.newpipe.R; import org.schabi.newpipe.database.subscription.SubscriptionEntity; @@ -134,6 +135,9 @@ public class SubscriptionFragment extends BaseStateFragment { void selected(T selectedItem); + void dropdownClicked(T selectedItem, PopupMenu menu); } private final Context context; diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 138503d39..1dea9633b 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -1,10 +1,16 @@ package org.schabi.newpipe.info_list.holder; +import android.content.Context; import android.support.v4.content.ContextCompat; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.PopupMenu; import android.widget.TextView; +import android.widget.Toast; import com.nostra13.universalimageloader.core.DisplayImageOptions; @@ -13,7 +19,12 @@ import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.player.BackgroundPlayer; +import org.schabi.newpipe.player.PopupVideoPlayer; +import org.schabi.newpipe.playlist.PlayQueue; +import org.schabi.newpipe.playlist.SinglePlayQueue; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.NavigationHelper; public class StreamMiniInfoItemHolder extends InfoItemHolder { @@ -21,6 +32,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { public final TextView itemVideoTitleView; public final TextView itemUploaderView; public final TextView itemDurationView; + public final ImageButton itemActionDropdown; StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { super(infoItemBuilder, layoutId, parent); @@ -29,6 +41,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView); itemUploaderView = itemView.findViewById(R.id.itemUploaderView); itemDurationView = itemView.findViewById(R.id.itemDurationView); + itemActionDropdown = itemView.findViewById(R.id.itemActionDropdown); } public StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { @@ -67,6 +80,84 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { } } }); + + switch (item.stream_type) { + case AUDIO_STREAM: + case VIDEO_STREAM: + case FILE: + enableActionDropdown(item); + break; + case LIVE_STREAM: + case AUDIO_LIVE_STREAM: + case NONE: + default: + break; + } + } + + private void enableActionDropdown(final StreamInfoItem item) { + itemActionDropdown.setVisibility(View.VISIBLE); + itemActionDropdown.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + final PopupMenu actionMenu = getStreamDropdown(itemBuilder.getContext(), itemActionDropdown, item); + if (itemBuilder.getOnStreamSelectedListener() != null) { + itemBuilder.getOnStreamSelectedListener().dropdownClicked(item, actionMenu); + } + actionMenu.show(); + } + }); + } + + private PopupMenu getStreamDropdown(final Context context, final View anchor, final StreamInfoItem infoItem) { + PopupMenu actionMenu = new PopupMenu(context, anchor); + + final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_btn_text); + mainPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnMainPlayer(context, new SinglePlayQueue(infoItem)); + return true; + } + }); + + final MenuItem popupPlay = actionMenu.getMenu().add(R.string.controls_popup_title); + popupPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnPopupPlayer(context, new SinglePlayQueue(infoItem)); + return true; + } + }); + + final MenuItem backgroundPlay = actionMenu.getMenu().add(R.string.controls_background_title); + backgroundPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnBackgroundPlayer(context, new SinglePlayQueue(infoItem)); + return true; + } + }); + + final MenuItem backgroundEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_background); + backgroundEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem)); + return true; + } + }); + + final MenuItem popupEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_popup); + popupEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(infoItem)); + return true; + } + }); + + return actionMenu; } /** diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 25735330d..cfed78179 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -48,6 +48,7 @@ import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.helper.LockManager; import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.util.ListHelper; +import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ThemeHelper; import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; @@ -130,16 +131,6 @@ public final class BackgroundPlayer extends Service { /*////////////////////////////////////////////////////////////////////////// // Actions //////////////////////////////////////////////////////////////////////////*/ - - public void openControl(final Context context) { - Intent intent = new Intent(context, BackgroundPlayerActivity.class); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } - context.startActivity(intent); - context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); - } - private void onClose() { if (DEBUG) Log.d(TAG, "onClose() called"); @@ -470,7 +461,7 @@ public final class BackgroundPlayer extends Service { onVideoPlayPause(); break; case ACTION_OPEN_CONTROLS: - openControl(getApplicationContext()); + NavigationHelper.openBackgroundPlayerControl(getApplicationContext()); break; case ACTION_REPEAT: onRepeatClicked(); diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 71068dbea..babbdbff5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -318,15 +318,6 @@ public final class PopupVideoPlayer extends Service { stopSelf(); } - public void openControl(final Context context) { - Intent intent = new Intent(context, PopupVideoPlayerActivity.class); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } - context.startActivity(intent); - context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); - } - /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ @@ -622,7 +613,7 @@ public final class PopupVideoPlayer extends Service { onVideoPlayPause(); break; case ACTION_OPEN_CONTROLS: - openControl(getApplicationContext()); + NavigationHelper.openPopupPlayerControl(getApplicationContext()); break; case ACTION_REPEAT: onRepeatClicked(); diff --git a/app/src/main/java/org/schabi/newpipe/playlist/SinglePlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/SinglePlayQueue.java index fc68e931a..ae74528eb 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/SinglePlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/SinglePlayQueue.java @@ -1,12 +1,21 @@ package org.schabi.newpipe.playlist; import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; import java.util.Collections; public final class SinglePlayQueue extends PlayQueue { + public SinglePlayQueue(final StreamInfoItem item) { + this(new PlayQueueItem(item)); + } + public SinglePlayQueue(final StreamInfo info) { - super(0, Collections.singletonList(new PlayQueueItem(info))); + this(new PlayQueueItem(info)); + } + + private SinglePlayQueue(final PlayQueueItem playQueueItem) { + super(0, Collections.singletonList(playQueueItem)); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index a68494706..07fc4ba7b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -5,10 +5,11 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Build; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; -import android.support.v7.app.AppCompatActivity; +import android.widget.Toast; import com.nostra13.universalimageloader.core.ImageLoader; @@ -28,7 +29,12 @@ import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment; import org.schabi.newpipe.fragments.list.search.SearchFragment; import org.schabi.newpipe.history.HistoryActivity; +import org.schabi.newpipe.player.BackgroundPlayer; +import org.schabi.newpipe.player.BackgroundPlayerActivity; import org.schabi.newpipe.player.BasePlayer; +import org.schabi.newpipe.player.MainVideoPlayer; +import org.schabi.newpipe.player.PopupVideoPlayer; +import org.schabi.newpipe.player.PopupVideoPlayerActivity; import org.schabi.newpipe.player.VideoPlayer; import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.settings.SettingsActivity; @@ -77,6 +83,29 @@ public class NavigationHelper { .putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch); } + public static void playOnMainPlayer(final Context context, final PlayQueue queue) { + context.startActivity(getPlayerIntent(context, MainVideoPlayer.class, queue)); + } + + public static void playOnPopupPlayer(final Context context, final PlayQueue queue) { + Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); + context.startService(getPlayerIntent(context, PopupVideoPlayer.class, queue)); + } + + public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue) { + Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show(); + context.startService(getPlayerIntent(context, BackgroundPlayer.class, queue)); + } + + public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue) { + Toast.makeText(context, R.string.popup_playing_append, Toast.LENGTH_SHORT).show(); + context.startService(getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue)); + } + + public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue) { + Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show(); + context.startService(getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue)); + } /*////////////////////////////////////////////////////////////////////////// // Through FragmentManager //////////////////////////////////////////////////////////////////////////*/ @@ -230,6 +259,23 @@ public class NavigationHelper { return true; } + public static void openBackgroundPlayerControl(final Context context) { + openServicePlayerControl(context, BackgroundPlayerActivity.class); + } + + public static void openPopupPlayerControl(final Context context) { + openServicePlayerControl(context, PopupVideoPlayerActivity.class); + } + + private static void openServicePlayerControl(final Context context, final Class clazz) { + final Intent intent = new Intent(context, clazz); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + context.startActivity(intent); + context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + } + /*////////////////////////////////////////////////////////////////////////// // Link handling //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..22acc550088d98f9d98ced517de75b5e8ead3c7c GIT binary patch literal 132 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B5}8r;B4q1>@UGdwClSM3^70uIQ21 z47_&cJkx~*+Z%R2IKOxGVZM^fH+7^Mmwc{S?NKJ@tD@b{HffqWt7pJW9xm}`3bIR@ eOD6DrGLJi>8B(9~m^Tk-EQ6=3pUXO@geCy;;w$k0 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..67f07e473442d1f5bd5dc486a42ce4bedf40b425 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K;ucwP+h{y4_mv-_VP!M4WR9aA@ zB9L2TV$k7bqBi~Tf{Y7K)@EPe&B(sMnUQ&cV?}1b!p)H;o34MJ iAa}{R%7g#se7OLo1Hz}Zd6xqXX7F_Nb6Mw<&;$T!hAw^p literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..0e4f2f6ea0564fe9d6ebdb4bea0df48aced9de0f GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1V^0^ykP60RiERoU4^0niv(+!U z$~^Ty`=RMavMX4xeQ2uS{l}xxJ=eVCD820|N5W*kJjJ67$C*FG*QAUgq7iWp7Wo)^m*JsBN#kg L{an^LB{Ts5on9xv literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..9f10aa2759a2e53beb129849c9b0e6ee5f7ae97b GIT binary patch literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0DBu^K|kP61PR}A@_6b0BGTJ|c& z&2zArUdgs%qoJD@@9neO|NgtXV7^DW)MQ81qkHN%-79zXF}ZeKv~Tuphvz#4Z3DJ9 zo+<2n{rTU)UWQJm<&*zc>PM$^=S_g|6%VwoI2{_d>%srJ7&|&vk-TbVh<5S;l;%8hhyk$Kc9U$ho zN!;a@as7tMKN^!>mP|UmC!qU^(pC>$p{$8fOT;)oR_iUC=UG0_`?VR+LIzJ)KbLh* G2~7aOZ8w1c literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_more_vert_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_more_vert_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..94d5ab98c6d3b3548536f04a2d635fb5c4a05b88 GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawrg*wIhEy=Vy=KqV>>$DV@#Ph3 zmm-(IJxlAmHdKggydBZHA?LrN(f;zraK2yFla}<&ocsFTF&FO*>g+$WJLfsbvhR#o zqU5?6`INL2k&sND<*?!p=%CFlpb5%Rrp)D#!8^w7WtAYxjJ8qaZaq8!Lha3*C xpO9i0)g!jJUB|8aV$u`{=V%64OvRJ&URzP%pP$_?yn*gx@O1TaS?83{1OO~yQ~m$| literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d32281307232fefc363e19d9d2728af62b440a8d GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw=6kw0hGg7(d(E5ckb#KX!!teA z8+IJIbSd_mp~lgrEob_e9cxnR1kZ18I^5b~uj)DJpG>-%nby>)6J_QWKRvd(S?`Ri~_DWys(>8rgyQv zG%aCaz>Js9>y=L)eUVgRxJx3oN6$@sv5NTO?u&4dNh*K$SbOB%sZae;9uIUXgQu&X J%Q~loCIECSTxI|O literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..4642a3b66e690a25544c7a65b5484607c76e9e4d GIT binary patch literal 272 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcgu6w#ThEy=Vz2TVE>>%O(aeKF| z&%}AAoaYX07I~@2Qzf){=E~b*b6q_X?JD-aXZn{o>8SH070*d=@0yRCD&F7PI4|Iy zgr>dT{)s<*uuMbP?bJzL!+Uj>du1MOpP(^y_vJlXtsXdjo_CdTHuJx`L9zyy z_bjw};Pttx=xP2JS*8CI?{O?lw)*qOUytSD#5&t0XL`98ez{Y*;jMY`-`}^VtDMwJ z5#Pjpv-OQrnIiiQFtvL^tj8o3AbA2TH#1Zw0j|Uo=)7NS4apHKER!-ifL>(qboFyt I=akR{05Df}#Q*>R literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_more_vert_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_more_vert_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..2f2cb3d004f6e828917b5f6b1decc7fe5d99b445 GIT binary patch literal 305 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcget5b#hE&{od&7{g$w9>R;>#mP zB_2gJIy{WxDHIfl;54zZ?(Dj(@bmNJ>M(=pX9D&J05vf@IA0RFIB;_s@2aW4t^P!s z?~T5q@wxa<`?f89Li_gpTlKB*dwsOb9#+$?m6!M>7BklVVk|aEPh1DJ$>V{EJl=XH?V({#-awY-(QL|0?dORlc*XtSl(} z&d3n4k$a}x{MZxU-fiZ-$+m%8aD#C}>V?${Gwe!OwoRt{8X v))3p#FqiR-(={Oe2E;4(u$C!;+}~cs6m#}#(>L)Q#vrbztDnm{r-UW|mL!4d literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/list_stream_item.xml b/app/src/main/res/layout/list_stream_item.xml index cb1ac6fe2..f09ab89be 100644 --- a/app/src/main/res/layout/list_stream_item.xml +++ b/app/src/main/res/layout/list_stream_item.xml @@ -7,7 +7,9 @@ android:layout_height="@dimen/video_item_search_height" android:background="?attr/selectableItemBackground" android:clickable="true" - android:padding="@dimen/video_item_search_padding"> + android:focusable="true" + android:paddingTop="@dimen/video_item_search_padding" + android:paddingBottom="@dimen/video_item_search_padding"> + + + android:focusable="true" + android:paddingTop="@dimen/video_item_search_padding" + android:paddingBottom="@dimen/video_item_search_padding"> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7092feaf5..1d93b61f1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -310,4 +310,6 @@ Details Audio Settings Hold To Enqueue + Enqueue on Background + Enqueue on Popup diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 8f0bb02cd..85549c8c5 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -27,6 +27,7 @@ @drawable/ic_history_black_24dp @drawable/ic_drag_handle_black_24dp @drawable/ic_fiber_manual_record_black_24dp + @drawable/ic_more_vert_black_24dp @color/light_separator_color @color/light_contrast_background_color @@ -65,6 +66,7 @@ @drawable/ic_history_white_24dp @drawable/ic_drag_handle_white_24dp @drawable/ic_fiber_manual_record_white_24dp + @drawable/ic_more_vert_white_24dp @color/dark_separator_color @color/dark_contrast_background_color From b8a17580c5da46c4489ee7d6411dd71a9d8fe057 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Wed, 1 Nov 2017 23:38:18 -0700 Subject: [PATCH 05/13] -Added play queue dropdown to channel info items. -Added play queue dropdown to playlist info items. -Added Channel Play Queue. -Renamed External Play Queue to Playlist Play Queue. -Modified Playlist Play Queue to allow loading from initial state. --- .../list/playlist/PlaylistFragment.java | 8 +- .../holder/ChannelMiniInfoItemHolder.java | 57 +++++++ .../holder/PlaylistInfoItemHolder.java | 56 +++++++ .../holder/StreamMiniInfoItemHolder.java | 51 +++--- .../player/playback/MediaSourceManager.java | 2 +- .../newpipe/playlist/ChannelPlayQueue.java | 151 ++++++++++++++++++ ...lPlayQueue.java => PlaylistPlayQueue.java} | 64 ++++++-- app/src/main/res/layout/list_channel_item.xml | 33 +++- .../res/layout/list_channel_mini_item.xml | 26 ++- .../main/res/layout/list_playlist_item.xml | 26 ++- app/src/main/res/layout/list_stream_item.xml | 2 +- .../main/res/layout/list_stream_mini_item.xml | 2 + 12 files changed, 431 insertions(+), 47 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java rename app/src/main/java/org/schabi/newpipe/playlist/{ExternalPlayQueue.java => PlaylistPlayQueue.java} (55%) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 9d57617d3..0c5b002e3 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.fragments.list.playlist; -import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; @@ -24,10 +23,7 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; -import org.schabi.newpipe.player.BackgroundPlayer; -import org.schabi.newpipe.player.MainVideoPlayer; -import org.schabi.newpipe.player.PopupVideoPlayer; -import org.schabi.newpipe.playlist.ExternalPlayQueue; +import org.schabi.newpipe.playlist.PlaylistPlayQueue; import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; @@ -185,7 +181,7 @@ public class PlaylistFragment extends BaseListInfoFragment { } private PlayQueue getPlayQueue() { - return new ExternalPlayQueue( + return new PlaylistPlayQueue( currentInfo.service_id, currentInfo.url, currentInfo.next_streams_url, diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java index 9aef6dbd2..30dc3d8bc 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java @@ -1,7 +1,11 @@ package org.schabi.newpipe.info_list.holder; +import android.content.Context; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.PopupMenu; import android.widget.TextView; import com.nostra13.universalimageloader.core.DisplayImageOptions; @@ -10,7 +14,9 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.playlist.ChannelPlayQueue; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.NavigationHelper; import de.hdodenhof.circleimageview.CircleImageView; @@ -18,6 +24,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { public final CircleImageView itemThumbnailView; public final TextView itemTitleView; public final TextView itemAdditionalDetailView; + public final ImageButton itemActionDropdown; ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { super(infoItemBuilder, layoutId, parent); @@ -25,6 +32,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemTitleView = itemView.findViewById(R.id.itemTitleView); itemAdditionalDetailView = itemView.findViewById(R.id.itemAdditionalDetails); + itemActionDropdown = itemView.findViewById(R.id.itemActionDropdown); } public ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { @@ -50,6 +58,55 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { } } }); + + enableActionDropdown(item); + } + + private void enableActionDropdown(final ChannelInfoItem item) { + itemActionDropdown.setVisibility(View.VISIBLE); + itemActionDropdown.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + final PopupMenu actionMenu = getStreamDropdown(itemBuilder.getContext(), itemActionDropdown, item); + if (itemBuilder.getOnChannelSelectedListener() != null) { + itemBuilder.getOnChannelSelectedListener().dropdownClicked(item, actionMenu); + } + actionMenu.show(); + } + }); + } + + private PopupMenu getStreamDropdown(final Context context, final View anchor, final ChannelInfoItem infoItem) { + PopupMenu actionMenu = new PopupMenu(context, anchor); + + final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_all); + mainPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnMainPlayer(context, new ChannelPlayQueue(infoItem)); + return true; + } + }); + + final MenuItem popupPlay = actionMenu.getMenu().add(R.string.controls_popup_title); + popupPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnPopupPlayer(context, new ChannelPlayQueue(infoItem)); + return true; + } + }); + + final MenuItem backgroundPlay = actionMenu.getMenu().add(R.string.controls_background_title); + backgroundPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnBackgroundPlayer(context, new ChannelPlayQueue(infoItem)); + return true; + } + }); + + return actionMenu; } protected String getDetailLine(final ChannelInfoItem item) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java index 3c29a4b76..5e0f44939 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java @@ -1,8 +1,12 @@ package org.schabi.newpipe.info_list.holder; +import android.content.Context; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.PopupMenu; import android.widget.TextView; import com.nostra13.universalimageloader.core.DisplayImageOptions; @@ -11,12 +15,15 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.playlist.PlaylistPlayQueue; +import org.schabi.newpipe.util.NavigationHelper; public class PlaylistInfoItemHolder extends InfoItemHolder { public final ImageView itemThumbnailView; public final TextView itemStreamCountView; public final TextView itemTitleView; public final TextView itemUploaderView; + public final ImageButton itemActionDropdown; public PlaylistInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { super(infoItemBuilder, R.layout.list_playlist_item, parent); @@ -25,6 +32,7 @@ public class PlaylistInfoItemHolder extends InfoItemHolder { itemTitleView = itemView.findViewById(R.id.itemTitleView); itemStreamCountView = itemView.findViewById(R.id.itemStreamCountView); itemUploaderView = itemView.findViewById(R.id.itemUploaderView); + itemActionDropdown = itemView.findViewById(R.id.itemActionDropdown); } @Override @@ -47,8 +55,56 @@ public class PlaylistInfoItemHolder extends InfoItemHolder { } } }); + + enableActionDropdown(item); } + private void enableActionDropdown(final PlaylistInfoItem item) { + itemActionDropdown.setVisibility(View.VISIBLE); + itemActionDropdown.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + final PopupMenu actionMenu = getStreamDropdown(itemBuilder.getContext(), itemActionDropdown, item); + if (itemBuilder.getOnPlaylistSelectedListener() != null) { + itemBuilder.getOnPlaylistSelectedListener().dropdownClicked(item, actionMenu); + } + actionMenu.show(); + } + }); + } + + private PopupMenu getStreamDropdown(final Context context, final View anchor, final PlaylistInfoItem infoItem) { + PopupMenu actionMenu = new PopupMenu(context, anchor); + + final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_all); + mainPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnMainPlayer(context, new PlaylistPlayQueue(infoItem)); + return true; + } + }); + + final MenuItem popupPlay = actionMenu.getMenu().add(R.string.controls_popup_title); + popupPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnPopupPlayer(context, new PlaylistPlayQueue(infoItem)); + return true; + } + }); + + final MenuItem backgroundPlay = actionMenu.getMenu().add(R.string.controls_background_title); + backgroundPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnBackgroundPlayer(context, new PlaylistPlayQueue(infoItem)); + return true; + } + }); + + return actionMenu; + } /** * Display options for playlist thumbnails */ diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 1dea9633b..6c399e481 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -2,7 +2,6 @@ package org.schabi.newpipe.info_list.holder; import android.content.Context; import android.support.v4.content.ContextCompat; -import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -10,7 +9,6 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.TextView; -import android.widget.Toast; import com.nostra13.universalimageloader.core.DisplayImageOptions; @@ -19,9 +17,6 @@ import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.info_list.InfoItemBuilder; -import org.schabi.newpipe.player.BackgroundPlayer; -import org.schabi.newpipe.player.PopupVideoPlayer; -import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.SinglePlayQueue; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; @@ -91,11 +86,13 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { case AUDIO_LIVE_STREAM: case NONE: default: + disableActionDropdown(); break; } } private void enableActionDropdown(final StreamInfoItem item) { + itemActionDropdown.setClickable(true); itemActionDropdown.setVisibility(View.VISIBLE); itemActionDropdown.setOnClickListener(new View.OnClickListener() { @Override @@ -109,10 +106,34 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { }); } + private void disableActionDropdown() { + itemActionDropdown.setVisibility(View.GONE); + itemActionDropdown.setClickable(false); + itemActionDropdown.setOnClickListener(null); + } + private PopupMenu getStreamDropdown(final Context context, final View anchor, final StreamInfoItem infoItem) { PopupMenu actionMenu = new PopupMenu(context, anchor); - final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_btn_text); + final MenuItem backgroundEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_background); + backgroundEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem)); + return true; + } + }); + + final MenuItem popupEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_popup); + popupEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(infoItem)); + return true; + } + }); + + final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_all); mainPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem menuItem) { @@ -139,24 +160,6 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { } }); - final MenuItem backgroundEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_background); - backgroundEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem)); - return true; - } - }); - - final MenuItem popupEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_popup); - popupEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(infoItem)); - return true; - } - }); - return actionMenu; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index 2e4e4af5c..04f1606fa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -157,7 +157,7 @@ public class MediaSourceManager { } private void onPlayQueueChanged(final PlayQueueEvent event) { - if (playQueue.isEmpty()) { + if (playQueue.isEmpty() && playQueue.isComplete()) { playbackListener.shutdown(); return; } diff --git a/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java new file mode 100644 index 000000000..c48f76127 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java @@ -0,0 +1,151 @@ +package org.schabi.newpipe.playlist; + +import android.util.Log; + +import org.schabi.newpipe.extractor.InfoItem; +import org.schabi.newpipe.extractor.ListExtractor; +import org.schabi.newpipe.extractor.channel.ChannelInfo; +import org.schabi.newpipe.extractor.channel.ChannelInfoItem; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.schabi.newpipe.util.ExtractorHelper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import io.reactivex.SingleObserver; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +public final class ChannelPlayQueue extends PlayQueue { + private final String TAG = "ChannelPlayQueue@" + Integer.toHexString(hashCode()); + + private boolean isInitial; + private boolean isComplete; + + private int serviceId; + private String baseUrl; + private String nextUrl; + + private transient Disposable fetchReactor; + + public ChannelPlayQueue(final ChannelInfoItem item) { + this(item.service_id, item.url, item.url, Collections.emptyList(), 0); + } + + public ChannelPlayQueue(final int serviceId, + final String url, + final String nextPageUrl, + final List streams, + final int index) { + super(index, extractChannelItems(streams)); + + this.baseUrl = url; + this.nextUrl = nextPageUrl; + this.serviceId = serviceId; + + this.isInitial = streams.isEmpty(); + this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty()); + } + + @Override + public boolean isComplete() { + return isComplete; + } + + @Override + public void fetch() { + if (isInitial) { + ExtractorHelper.getChannelInfo(this.serviceId, this.baseUrl, false) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getChannelInitialObserver()); + } else { + ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextUrl) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getChannelNextItemsObserver()); + } + } + + private SingleObserver getChannelInitialObserver() { + return new SingleObserver() { + @Override + public void onSubscribe(@NonNull Disposable d) { + if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { + d.dispose(); + } else { + fetchReactor = d; + } + } + + @Override + public void onSuccess(@NonNull ChannelInfo result) { + if (!result.has_more_streams) isComplete = true; + nextUrl = result.next_streams_url; + + append(extractChannelItems(result.related_streams)); + + isInitial = false; + fetchReactor.dispose(); + fetchReactor = null; + } + + @Override + public void onError(@NonNull Throwable e) { + Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e); + isComplete = true; + append(); // Notify change + } + }; + } + + private SingleObserver getChannelNextItemsObserver() { + return new SingleObserver() { + @Override + public void onSubscribe(@NonNull Disposable d) { + if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { + d.dispose(); + } else { + fetchReactor = d; + } + } + + @Override + public void onSuccess(@NonNull ListExtractor.NextItemsResult result) { + if (!result.hasMoreStreams()) isComplete = true; + nextUrl = result.nextItemsUrl; + + append(extractChannelItems(result.nextItemsList)); + + fetchReactor.dispose(); + fetchReactor = null; + } + + @Override + public void onError(@NonNull Throwable e) { + Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e); + isComplete = true; + append(); // Notify change + } + }; + } + + @Override + public void dispose() { + super.dispose(); + if (fetchReactor != null) fetchReactor.dispose(); + } + + private static List extractChannelItems(final List infos) { + List result = new ArrayList<>(); + for (final InfoItem stream : infos) { + if (stream instanceof StreamInfoItem) { + result.add(new PlayQueueItem((StreamInfoItem) stream)); + } + } + return result; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/playlist/ExternalPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java similarity index 55% rename from app/src/main/java/org/schabi/newpipe/playlist/ExternalPlayQueue.java rename to app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java index 019b684d4..39ac6517d 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/ExternalPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java @@ -4,6 +4,8 @@ import android.util.Log; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; +import org.schabi.newpipe.extractor.playlist.PlaylistInfo; +import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.util.ExtractorHelper; @@ -17,9 +19,10 @@ import io.reactivex.annotations.NonNull; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -public final class ExternalPlayQueue extends PlayQueue { - private final String TAG = "ExternalPlayQueue@" + Integer.toHexString(hashCode()); +public final class PlaylistPlayQueue extends PlayQueue { + private final String TAG = "PlaylistPlayQueue@" + Integer.toHexString(hashCode()); + private boolean isInitial; private boolean isComplete; private int serviceId; @@ -28,7 +31,11 @@ public final class ExternalPlayQueue extends PlayQueue { private transient Disposable fetchReactor; - public ExternalPlayQueue(final int serviceId, + public PlaylistPlayQueue(final PlaylistInfoItem item) { + this(item.service_id, item.url, item.url, Collections.emptyList(), 0); + } + + public PlaylistPlayQueue(final int serviceId, final String url, final String nextPageUrl, final List streams, @@ -39,7 +46,8 @@ public final class ExternalPlayQueue extends PlayQueue { this.nextUrl = nextPageUrl; this.serviceId = serviceId; - this.isComplete = nextPageUrl == null || nextPageUrl.isEmpty(); + this.isInitial = streams.isEmpty(); + this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty()); } @Override @@ -49,13 +57,51 @@ public final class ExternalPlayQueue extends PlayQueue { @Override public void fetch() { - ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(getPlaylistObserver()); + if (isInitial) { + ExtractorHelper.getPlaylistInfo(this.serviceId, this.baseUrl, false) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getInitialPlaylistObserver()); + } else { + ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getPlaylistNextItemsObserver()); + } } - private SingleObserver getPlaylistObserver() { + private SingleObserver getInitialPlaylistObserver() { + return new SingleObserver() { + @Override + public void onSubscribe(@NonNull Disposable d) { + if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { + d.dispose(); + } else { + fetchReactor = d; + } + } + + @Override + public void onSuccess(@NonNull PlaylistInfo result) { + if (!result.has_more_streams) isComplete = true; + nextUrl = result.next_streams_url; + + append(extractPlaylistItems(result.related_streams)); + + fetchReactor.dispose(); + fetchReactor = null; + } + + @Override + public void onError(@NonNull Throwable e) { + Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e); + isComplete = true; + append(); // Notify change + } + }; + } + + private SingleObserver getPlaylistNextItemsObserver() { return new SingleObserver() { @Override public void onSubscribe(@NonNull Disposable d) { diff --git a/app/src/main/res/layout/list_channel_item.xml b/app/src/main/res/layout/list_channel_item.xml index 3b4b71dc9..90f88b26f 100644 --- a/app/src/main/res/layout/list_channel_item.xml +++ b/app/src/main/res/layout/list_channel_item.xml @@ -7,7 +7,9 @@ android:layout_height="@dimen/video_item_search_height" android:background="?attr/selectableItemBackground" android:clickable="true" - android:padding="@dimen/video_item_search_padding"> + android:focusable="true" + android:paddingTop="@dimen/video_item_search_padding" + android:paddingBottom="@dimen/video_item_search_padding"> + + + tools:text="Channel Title, Lorem ipsum" /> + tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" /> + android:focusable="true" + android:paddingTop="@dimen/video_item_search_padding" + android:paddingBottom="@dimen/video_item_search_padding"> + + + android:focusable="true" + android:paddingTop="@dimen/video_item_search_padding" + android:paddingBottom="@dimen/video_item_search_padding"> + + Date: Thu, 2 Nov 2017 12:30:26 -0700 Subject: [PATCH 06/13] -Added fast seeking on background notification when play queue size is 1. -Fixed player intent with quality selection not used in detail fragment. -Fixed window index not reset on sync when not playing. -Fix dropdown play string for stream info item. --- .../fragments/detail/VideoDetailFragment.java | 18 +++++--- .../holder/StreamMiniInfoItemHolder.java | 2 +- .../newpipe/player/BackgroundPlayer.java | 41 +++++++++++++++---- .../org/schabi/newpipe/player/BasePlayer.java | 6 +-- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 265e22319..f6753f128 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -66,6 +66,7 @@ import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.player.BackgroundPlayer; import org.schabi.newpipe.player.MainVideoPlayer; import org.schabi.newpipe.player.PopupVideoPlayer; +import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.old.PlayVideoActivity; import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.SinglePlayQueue; @@ -800,7 +801,11 @@ public class VideoDetailFragment extends BaseStateFragment implement if (append) { NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue); } else { - NavigationHelper.playOnPopupPlayer(activity, itemQueue); + Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); + final Intent intent = NavigationHelper.getPlayerIntent( + activity, PopupVideoPlayer.class, itemQueue, getSelectedVideoStream().resolution + ); + activity.startService(intent); } } @@ -863,20 +868,21 @@ public class VideoDetailFragment extends BaseStateFragment implement } private void openNormalPlayer(VideoStream selectedVideoStream) { - boolean useOldPlayer = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(getString(R.string.use_old_player_key), false) - || (Build.VERSION.SDK_INT < 16); + Intent mIntent; + boolean useOldPlayer = PlayerHelper.isUsingOldPlayer(activity) || (Build.VERSION.SDK_INT < 16); if (!useOldPlayer) { // ExoPlayer - NavigationHelper.playOnMainPlayer(activity, new SinglePlayQueue(currentInfo)); + final PlayQueue playQueue = new SinglePlayQueue(currentInfo); + mIntent = NavigationHelper.getPlayerIntent(activity, MainVideoPlayer.class, playQueue, getSelectedVideoStream().resolution); } else { // Internal Player - final Intent mIntent = new Intent(activity, PlayVideoActivity.class) + mIntent = new Intent(activity, PlayVideoActivity.class) .putExtra(PlayVideoActivity.VIDEO_TITLE, currentInfo.name) .putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.url) .putExtra(PlayVideoActivity.VIDEO_URL, currentInfo.url) .putExtra(PlayVideoActivity.START_POSITION, currentInfo.start_position); - startActivity(mIntent); } + startActivity(mIntent); } private void openExternalVideoPlayer(VideoStream selectedVideoStream) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 6c399e481..a910041d0 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -133,7 +133,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { } }); - final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_all); + final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_btn_text); mainPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem menuItem) { diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index cfed78179..dfbb98b95 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -69,6 +69,10 @@ public final class BackgroundPlayer extends Service { public static final String ACTION_REPEAT = "org.schabi.newpipe.player.BackgroundPlayer.REPEAT"; public static final String ACTION_PLAY_NEXT = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_NEXT"; public static final String ACTION_PLAY_PREVIOUS = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_PREVIOUS"; + public static final String ACTION_FAST_REWIND = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND"; + public static final String ACTION_FAST_FORWARD = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD"; + + public static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource"; private BasePlayerImpl basePlayerImpl; private LockManager lockManager; @@ -182,6 +186,8 @@ public final class BackgroundPlayer extends Service { } private void setupNotification(RemoteViews remoteViews) { + if (basePlayerImpl == null) return; + remoteViews.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle()); remoteViews.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName()); @@ -194,10 +200,21 @@ public final class BackgroundPlayer extends Service { remoteViews.setOnClickPendingIntent(R.id.notificationRepeat, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT)); - remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT)); - remoteViews.setOnClickPendingIntent(R.id.notificationFForward, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT)); + if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) { + remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_previous); + remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_next); + remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, + PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT)); + remoteViews.setOnClickPendingIntent(R.id.notificationFForward, + PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT)); + } else { + remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_rewind); + remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_fastforward); + remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, + PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT)); + remoteViews.setOnClickPendingIntent(R.id.notificationFForward, + PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT)); + } setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode()); } @@ -232,17 +249,15 @@ public final class BackgroundPlayer extends Service { //////////////////////////////////////////////////////////////////////////*/ private void setRepeatModeIcon(final RemoteViews remoteViews, final int repeatMode) { - final String methodName = "setImageResource"; - switch (repeatMode) { case Player.REPEAT_MODE_OFF: - remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_off); + remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_off); break; case Player.REPEAT_MODE_ONE: - remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_one); + remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_one); break; case Player.REPEAT_MODE_ALL: - remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_all); + remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_all); break; } } @@ -441,6 +456,8 @@ public final class BackgroundPlayer extends Service { intentFilter.addAction(ACTION_REPEAT); intentFilter.addAction(ACTION_PLAY_PREVIOUS); intentFilter.addAction(ACTION_PLAY_NEXT); + intentFilter.addAction(ACTION_FAST_REWIND); + intentFilter.addAction(ACTION_FAST_FORWARD); intentFilter.addAction(Intent.ACTION_SCREEN_ON); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); @@ -472,6 +489,12 @@ public final class BackgroundPlayer extends Service { case ACTION_PLAY_PREVIOUS: onPlayPrevious(); break; + case ACTION_FAST_FORWARD: + onFastForward(); + break; + case ACTION_FAST_REWIND: + onFastRewind(); + break; case Intent.ACTION_SCREEN_ON: onScreenOnOff(true); break; diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index c24a46049..3da976991 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -646,7 +646,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen if (currentSourceIndex != playQueue.getIndex()) { Log.e(TAG, "Play Queue may be desynchronized: item index=[" + currentSourceIndex + "], queue index=[" + playQueue.getIndex() + "]"); - } else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex) { + } else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex || !isPlaying()) { final long startPos = info != null ? info.start_position : 0; if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos)); simpleExoPlayer.seekTo(currentSourceIndex, startPos); @@ -761,10 +761,6 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen } else { playQueue.setIndex(index); } - - if (!isPlaying()) { - onVideoPlayPause(); - } } public void seekBy(int milliSeconds) { From b32f149a1bd811598030ec3e128967d1fa576bce Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Thu, 2 Nov 2017 20:34:12 -0700 Subject: [PATCH 07/13] -Refactored Channel and Playlist PlayQueue into AbstractInfo playQueue. -Increase list item action dropdown padding. --- .../fragments/detail/VideoDetailFragment.java | 1 - .../playlist/AbstractInfoPlayQueue.java | 131 ++++++++++++++++++ .../newpipe/playlist/ChannelPlayQueue.java | 130 ++--------------- .../newpipe/playlist/PlaylistPlayQueue.java | 129 ++--------------- app/src/main/res/layout/list_channel_item.xml | 8 +- .../res/layout/list_channel_mini_item.xml | 6 +- .../main/res/layout/list_playlist_item.xml | 6 +- app/src/main/res/layout/list_stream_item.xml | 6 +- .../main/res/layout/list_stream_mini_item.xml | 6 +- 9 files changed, 171 insertions(+), 252 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index f6753f128..cbb658025 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -63,7 +63,6 @@ import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.history.HistoryListener; import org.schabi.newpipe.info_list.InfoItemBuilder; -import org.schabi.newpipe.player.BackgroundPlayer; import org.schabi.newpipe.player.MainVideoPlayer; import org.schabi.newpipe.player.PopupVideoPlayer; import org.schabi.newpipe.player.helper.PlayerHelper; diff --git a/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java new file mode 100644 index 000000000..74a4dc555 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java @@ -0,0 +1,131 @@ +package org.schabi.newpipe.playlist; + +import android.util.Log; + +import org.schabi.newpipe.extractor.InfoItem; +import org.schabi.newpipe.extractor.ListExtractor; +import org.schabi.newpipe.extractor.ListInfo; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import io.reactivex.SingleObserver; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; + +abstract class AbstractInfoPlayQueue extends PlayQueue { + boolean isInitial; + boolean isComplete; + + int serviceId; + String baseUrl; + String nextUrl; + + transient Disposable fetchReactor; + + AbstractInfoPlayQueue(final U item) { + this(item.service_id, item.url, item.url, Collections.emptyList(), 0); + } + + AbstractInfoPlayQueue(final int serviceId, + final String url, + final String nextPageUrl, + final List streams, + final int index) { + super(index, extractListItems(streams)); + + this.baseUrl = url; + this.nextUrl = nextPageUrl; + this.serviceId = serviceId; + + this.isInitial = streams.isEmpty(); + this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty()); + } + + abstract protected String getTag(); + + @Override + public boolean isComplete() { + return isComplete; + } + + SingleObserver getHeadListObserver() { + return new SingleObserver() { + @Override + public void onSubscribe(@NonNull Disposable d) { + if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { + d.dispose(); + } else { + fetchReactor = d; + } + } + + @Override + public void onSuccess(@NonNull T result) { + if (!result.has_more_streams) isComplete = true; + nextUrl = result.next_streams_url; + + append(extractListItems(result.related_streams)); + + fetchReactor.dispose(); + fetchReactor = null; + } + + @Override + public void onError(@NonNull Throwable e) { + Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e); + isComplete = true; + append(); // Notify change + } + }; + } + + SingleObserver getNextItemsObserver() { + return new SingleObserver() { + @Override + public void onSubscribe(@NonNull Disposable d) { + if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { + d.dispose(); + } else { + fetchReactor = d; + } + } + + @Override + public void onSuccess(@NonNull ListExtractor.NextItemsResult result) { + if (!result.hasMoreStreams()) isComplete = true; + nextUrl = result.nextItemsUrl; + + append(extractListItems(result.nextItemsList)); + + fetchReactor.dispose(); + fetchReactor = null; + } + + @Override + public void onError(@NonNull Throwable e) { + Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e); + isComplete = true; + append(); // Notify change + } + }; + } + + @Override + public void dispose() { + super.dispose(); + if (fetchReactor != null) fetchReactor.dispose(); + } + + private static List extractListItems(final List infos) { + List result = new ArrayList<>(); + for (final InfoItem stream : infos) { + if (stream instanceof StreamInfoItem) { + result.add(new PlayQueueItem((StreamInfoItem) stream)); + } + } + return result; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java index c48f76127..239024f28 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java @@ -1,151 +1,45 @@ package org.schabi.newpipe.playlist; -import android.util.Log; - import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; -import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.util.ExtractorHelper; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import io.reactivex.SingleObserver; import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.annotations.NonNull; -import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -public final class ChannelPlayQueue extends PlayQueue { - private final String TAG = "ChannelPlayQueue@" + Integer.toHexString(hashCode()); - - private boolean isInitial; - private boolean isComplete; - - private int serviceId; - private String baseUrl; - private String nextUrl; - - private transient Disposable fetchReactor; - +public final class ChannelPlayQueue extends AbstractInfoPlayQueue { public ChannelPlayQueue(final ChannelInfoItem item) { - this(item.service_id, item.url, item.url, Collections.emptyList(), 0); + super(item); } public ChannelPlayQueue(final int serviceId, - final String url, - final String nextPageUrl, - final List streams, - final int index) { - super(index, extractChannelItems(streams)); - - this.baseUrl = url; - this.nextUrl = nextPageUrl; - this.serviceId = serviceId; - - this.isInitial = streams.isEmpty(); - this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty()); + final String url, + final String nextPageUrl, + final List streams, + final int index) { + super(serviceId, url, nextPageUrl, streams, index); } @Override - public boolean isComplete() { - return isComplete; + protected String getTag() { + return "ChannelPlayQueue@" + Integer.toHexString(hashCode()); } @Override public void fetch() { - if (isInitial) { + if (this.isInitial) { ExtractorHelper.getChannelInfo(this.serviceId, this.baseUrl, false) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(getChannelInitialObserver()); + .subscribe(getHeadListObserver()); } else { ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextUrl) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(getChannelNextItemsObserver()); + .subscribe(getNextItemsObserver()); } } - - private SingleObserver getChannelInitialObserver() { - return new SingleObserver() { - @Override - public void onSubscribe(@NonNull Disposable d) { - if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { - d.dispose(); - } else { - fetchReactor = d; - } - } - - @Override - public void onSuccess(@NonNull ChannelInfo result) { - if (!result.has_more_streams) isComplete = true; - nextUrl = result.next_streams_url; - - append(extractChannelItems(result.related_streams)); - - isInitial = false; - fetchReactor.dispose(); - fetchReactor = null; - } - - @Override - public void onError(@NonNull Throwable e) { - Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e); - isComplete = true; - append(); // Notify change - } - }; - } - - private SingleObserver getChannelNextItemsObserver() { - return new SingleObserver() { - @Override - public void onSubscribe(@NonNull Disposable d) { - if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { - d.dispose(); - } else { - fetchReactor = d; - } - } - - @Override - public void onSuccess(@NonNull ListExtractor.NextItemsResult result) { - if (!result.hasMoreStreams()) isComplete = true; - nextUrl = result.nextItemsUrl; - - append(extractChannelItems(result.nextItemsList)); - - fetchReactor.dispose(); - fetchReactor = null; - } - - @Override - public void onError(@NonNull Throwable e) { - Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e); - isComplete = true; - append(); // Notify change - } - }; - } - - @Override - public void dispose() { - super.dispose(); - if (fetchReactor != null) fetchReactor.dispose(); - } - - private static List extractChannelItems(final List infos) { - List result = new ArrayList<>(); - for (final InfoItem stream : infos) { - if (stream instanceof StreamInfoItem) { - result.add(new PlayQueueItem((StreamInfoItem) stream)); - } - } - return result; - } } diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java index 39ac6517d..30edcec7c 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java @@ -1,150 +1,45 @@ package org.schabi.newpipe.playlist; -import android.util.Log; - import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; -import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.util.ExtractorHelper; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import io.reactivex.SingleObserver; import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.annotations.NonNull; -import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -public final class PlaylistPlayQueue extends PlayQueue { - private final String TAG = "PlaylistPlayQueue@" + Integer.toHexString(hashCode()); - - private boolean isInitial; - private boolean isComplete; - - private int serviceId; - private String baseUrl; - private String nextUrl; - - private transient Disposable fetchReactor; - +public final class PlaylistPlayQueue extends AbstractInfoPlayQueue { public PlaylistPlayQueue(final PlaylistInfoItem item) { - this(item.service_id, item.url, item.url, Collections.emptyList(), 0); + super(item); } public PlaylistPlayQueue(final int serviceId, - final String url, - final String nextPageUrl, - final List streams, - final int index) { - super(index, extractPlaylistItems(streams)); - - this.baseUrl = url; - this.nextUrl = nextPageUrl; - this.serviceId = serviceId; - - this.isInitial = streams.isEmpty(); - this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty()); + final String url, + final String nextPageUrl, + final List streams, + final int index) { + super(serviceId, url, nextPageUrl, streams, index); } @Override - public boolean isComplete() { - return isComplete; + protected String getTag() { + return "PlaylistPlayQueue@" + Integer.toHexString(hashCode()); } @Override public void fetch() { - if (isInitial) { + if (this.isInitial) { ExtractorHelper.getPlaylistInfo(this.serviceId, this.baseUrl, false) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(getInitialPlaylistObserver()); + .subscribe(getHeadListObserver()); } else { ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(getPlaylistNextItemsObserver()); + .subscribe(getNextItemsObserver()); } } - - private SingleObserver getInitialPlaylistObserver() { - return new SingleObserver() { - @Override - public void onSubscribe(@NonNull Disposable d) { - if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { - d.dispose(); - } else { - fetchReactor = d; - } - } - - @Override - public void onSuccess(@NonNull PlaylistInfo result) { - if (!result.has_more_streams) isComplete = true; - nextUrl = result.next_streams_url; - - append(extractPlaylistItems(result.related_streams)); - - fetchReactor.dispose(); - fetchReactor = null; - } - - @Override - public void onError(@NonNull Throwable e) { - Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e); - isComplete = true; - append(); // Notify change - } - }; - } - - private SingleObserver getPlaylistNextItemsObserver() { - return new SingleObserver() { - @Override - public void onSubscribe(@NonNull Disposable d) { - if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { - d.dispose(); - } else { - fetchReactor = d; - } - } - - @Override - public void onSuccess(@NonNull ListExtractor.NextItemsResult result) { - if (!result.hasMoreStreams()) isComplete = true; - nextUrl = result.nextItemsUrl; - - append(extractPlaylistItems(result.nextItemsList)); - - fetchReactor.dispose(); - fetchReactor = null; - } - - @Override - public void onError(@NonNull Throwable e) { - Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e); - isComplete = true; - append(); // Notify change - } - }; - } - - @Override - public void dispose() { - super.dispose(); - if (fetchReactor != null) fetchReactor.dispose(); - } - - private static List extractPlaylistItems(final List infos) { - List result = new ArrayList<>(); - for (final InfoItem stream : infos) { - if (stream instanceof StreamInfoItem) { - result.add(new PlayQueueItem((StreamInfoItem) stream)); - } - } - return result; - } } diff --git a/app/src/main/res/layout/list_channel_item.xml b/app/src/main/res/layout/list_channel_item.xml index 90f88b26f..4fa74387e 100644 --- a/app/src/main/res/layout/list_channel_item.xml +++ b/app/src/main/res/layout/list_channel_item.xml @@ -31,13 +31,13 @@ android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_alignParentTop="true" - android:layout_marginLeft="@dimen/video_item_search_image_right_margin" - android:layout_marginStart="@dimen/video_item_search_image_right_margin" + android:paddingLeft="@dimen/video_item_search_padding" + android:paddingStart="@dimen/video_item_search_padding" android:background="?attr/selectableItemBackground" android:src="?attr/more_vertical" android:visibility="gone" - tools:ignore="ContentDescription" - tools:visibility="visible" /> + tools:visibility="visible" + tools:ignore="ContentDescription,RtlSymmetry"/> + tools:ignore="ContentDescription,RtlSymmetry"/> + tools:ignore="ContentDescription,RtlSymmetry"/> + tools:ignore="ContentDescription,RtlSymmetry"/> + tools:ignore="ContentDescription,RtlSymmetry"/> Date: Fri, 3 Nov 2017 20:32:10 -0700 Subject: [PATCH 08/13] -Added scroll to fetch for external play queues. -Modified service player activity scrolling to be instantaneous when difference is too large. -Modified service player activity to no longer update metadata if nothing change when sync is called. --- .../newpipe/player/BackgroundPlayer.java | 1 + .../newpipe/player/MainVideoPlayer.java | 19 +++++++++- .../newpipe/player/PopupVideoPlayer.java | 1 + .../newpipe/player/ServicePlayerActivity.java | 36 ++++++++++++++++++- .../playlist/AbstractInfoPlayQueue.java | 8 ++--- 5 files changed, 59 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index dfbb98b95..443da74d4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -378,6 +378,7 @@ public final class BackgroundPlayer extends Service { @Override public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) { + if (currentItem == item && currentInfo == info) return; super.sync(item, info); resetNotification(); diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index c275e55a7..b91a0814b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -48,6 +48,7 @@ import com.google.android.exoplayer2.Player; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; +import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.PlayQueueItemBuilder; @@ -397,7 +398,7 @@ public final class MainVideoPlayer extends Activity { getControlsRoot().setVisibility(View.INVISIBLE); queueLayout.setVisibility(View.VISIBLE); - itemsList.smoothScrollToPosition(playQueue.getIndex()); + itemsList.scrollToPosition(playQueue.getIndex()); } private void onQueueClosed() { @@ -565,6 +566,9 @@ public final class MainVideoPlayer extends Activity { itemsList.setClickable(true); itemsList.setLongClickable(true); + itemsList.clearOnScrollListeners(); + itemsList.addOnScrollListener(getQueueScrollListener()); + itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); itemTouchHelper.attachToRecyclerView(itemsList); @@ -578,6 +582,19 @@ public final class MainVideoPlayer extends Activity { }); } + private OnScrollBelowItemsListener getQueueScrollListener() { + return new OnScrollBelowItemsListener() { + @Override + public void onScrolledDown(RecyclerView recyclerView) { + if (playQueue != null && !playQueue.isComplete()) { + playQueue.fetch(); + } else if (itemsList != null) { + itemsList.clearOnScrollListeners(); + } + } + }; + } + private ItemTouchHelper.SimpleCallback getItemTouchCallback() { return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) { @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index babbdbff5..48a435d37 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -573,6 +573,7 @@ public final class PopupVideoPlayer extends Service { @Override public void sync(@NonNull PlayQueueItem item, @Nullable StreamInfo info) { + if (currentItem == item && currentInfo == info) return; super.sync(item, info); updateMetadata(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index 4fe228dfc..98595b358 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer2.Player; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.PlayQueueItemBuilder; @@ -57,6 +58,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity private static final int PLAYBACK_SPEED_POPUP_MENU_GROUP_ID = 61; private static final int PLAYBACK_PITCH_POPUP_MENU_GROUP_ID = 97; + private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80; + private View rootView; private RecyclerView itemsList; @@ -225,6 +228,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity itemsList.setAdapter(player.getPlayQueueAdapter()); itemsList.setClickable(true); itemsList.setLongClickable(true); + itemsList.clearOnScrollListeners(); + itemsList.addOnScrollListener(getQueueScrollListener()); itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); itemTouchHelper.attachToRecyclerView(itemsList); @@ -345,6 +350,19 @@ public abstract class ServicePlayerActivity extends AppCompatActivity // Component Helpers //////////////////////////////////////////////////////////////////////////// + private OnScrollBelowItemsListener getQueueScrollListener() { + return new OnScrollBelowItemsListener() { + @Override + public void onScrolledDown(RecyclerView recyclerView) { + if (player != null && player.getPlayQueue() != null && !player.getPlayQueue().isComplete()) { + player.getPlayQueue().fetch(); + } else if (itemsList != null) { + itemsList.clearOnScrollListeners(); + } + } + }; + } + private ItemTouchHelper.SimpleCallback getItemTouchCallback() { return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) { @Override @@ -401,7 +419,23 @@ public abstract class ServicePlayerActivity extends AppCompatActivity } private void scrollToSelected() { - itemsList.smoothScrollToPosition(player.getPlayQueue().getIndex()); + if (player == null) return; + + final int currentPlayingIndex = player.getPlayQueue().getIndex(); + final int currentVisibleIndex; + if (itemsList.getLayoutManager() instanceof LinearLayoutManager) { + final LinearLayoutManager layout = ((LinearLayoutManager) itemsList.getLayoutManager()); + currentVisibleIndex = layout.findFirstVisibleItemPosition(); + } else { + currentVisibleIndex = 0; + } + + final int distance = Math.abs(currentPlayingIndex - currentVisibleIndex); + if (distance < SMOOTH_SCROLL_MAXIMUM_DISTANCE) { + itemsList.smoothScrollToPosition(currentPlayingIndex); + } else { + itemsList.scrollToPosition(currentPlayingIndex); + } } //////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java index 74a4dc555..b35271b8f 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java @@ -30,10 +30,10 @@ abstract class AbstractInfoPlayQueue ext } AbstractInfoPlayQueue(final int serviceId, - final String url, - final String nextPageUrl, - final List streams, - final int index) { + final String url, + final String nextPageUrl, + final List streams, + final int index) { super(index, extractListItems(streams)); this.baseUrl = url; From b883f313bae6aa81491661654dc43814554938e8 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Sat, 4 Nov 2017 10:12:38 -0700 Subject: [PATCH 09/13] -Fixed NPE when popup is updated during shutdown. --- .../main/java/org/schabi/newpipe/player/PopupVideoPlayer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 48a435d37..a3fd0f7a7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -362,6 +362,7 @@ public final class PopupVideoPlayer extends Service { } private void updatePopupSize(int width, int height) { + if (playerImpl == null) return; if (DEBUG) Log.d(TAG, "updatePopupSize() called with: width = [" + width + "], height = [" + height + "]"); width = (int) (width > maximumWidth ? maximumWidth : width < minimumWidth ? minimumWidth : width); From 7700cff5e51ea9d7b91e0d47ff80d9956e63da62 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Sat, 4 Nov 2017 11:30:01 -0700 Subject: [PATCH 10/13] -Added play buttons to channel fragment similar to playlist fragment. -Fixed abstract info play queue reloading the same initial page. -Fixed OOB on get item for abstract play queue. --- .../list/channel/ChannelFragment.java | 51 +++++ .../playlist/AbstractInfoPlayQueue.java | 7 +- .../newpipe/playlist/ChannelPlayQueue.java | 8 +- .../schabi/newpipe/playlist/PlayQueue.java | 2 +- .../newpipe/playlist/PlaylistPlayQueue.java | 8 +- app/src/main/res/layout/channel_header.xml | 192 ++++++++++++------ 6 files changed, 191 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 64875b17f..908997a11 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -3,6 +3,7 @@ package org.schabi.newpipe.fragments.list.channel; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -10,6 +11,7 @@ import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; import android.text.TextUtils; import android.util.Log; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -19,6 +21,7 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import com.jakewharton.rxbinding2.view.RxView; @@ -30,10 +33,14 @@ import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.fragments.subscription.SubscriptionService; +import org.schabi.newpipe.playlist.ChannelPlayQueue; +import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.PermissionHelper; import java.util.List; import java.util.concurrent.TimeUnit; @@ -69,6 +76,10 @@ public class ChannelFragment extends BaseListInfoFragment { private TextView headerSubscribersTextView; private Button headerSubscribeButton; + private Button headerPlayAllButton; + private Button headerPopupButton; + private Button headerBackgroundButton; + private MenuItem menuRssButton; public static ChannelFragment getInstance(int serviceId, String url, String name) { @@ -125,6 +136,10 @@ public class ChannelFragment extends BaseListInfoFragment { headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view); headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button); + headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_play_all_button); + headerPopupButton = headerRootLayout.findViewById(R.id.playlist_play_popup_button); + headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_play_bg_button); + return headerRootLayout; } @@ -391,6 +406,42 @@ public class ChannelFragment extends BaseListInfoFragment { if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose(); updateSubscription(result); monitorSubscription(result); + + headerPlayAllButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + NavigationHelper.playOnMainPlayer(activity, getPlayQueue()); + } + }); + headerPopupButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !PermissionHelper.checkSystemAlertWindowPermission(activity)) { + Toast toast = Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG); + TextView messageView = toast.getView().findViewById(android.R.id.message); + if (messageView != null) messageView.setGravity(Gravity.CENTER); + toast.show(); + return; + } + NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()); + } + }); + headerBackgroundButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()); + } + }); + } + + private PlayQueue getPlayQueue() { + return new ChannelPlayQueue( + currentInfo.service_id, + currentInfo.url, + currentInfo.next_streams_url, + infoListAdapter.getItemsList(), + 0 + ); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java index b35271b8f..74a68b880 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java @@ -26,7 +26,7 @@ abstract class AbstractInfoPlayQueue ext transient Disposable fetchReactor; AbstractInfoPlayQueue(final U item) { - this(item.service_id, item.url, item.url, Collections.emptyList(), 0); + this(item.service_id, item.url, null, Collections.emptyList(), 0); } AbstractInfoPlayQueue(final int serviceId, @@ -55,7 +55,7 @@ abstract class AbstractInfoPlayQueue ext return new SingleObserver() { @Override public void onSubscribe(@NonNull Disposable d) { - if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { + if (isComplete || !isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) { d.dispose(); } else { fetchReactor = d; @@ -64,6 +64,7 @@ abstract class AbstractInfoPlayQueue ext @Override public void onSuccess(@NonNull T result) { + isInitial = false; if (!result.has_more_streams) isComplete = true; nextUrl = result.next_streams_url; @@ -86,7 +87,7 @@ abstract class AbstractInfoPlayQueue ext return new SingleObserver() { @Override public void onSubscribe(@NonNull Disposable d) { - if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { + if (isComplete || isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) { d.dispose(); } else { fetchReactor = d; diff --git a/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java index 239024f28..3c615608c 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java @@ -16,10 +16,10 @@ public final class ChannelPlayQueue extends AbstractInfoPlayQueue streams, - final int index) { + final String url, + final String nextPageUrl, + final List streams, + final int index) { super(serviceId, url, nextPageUrl, streams, index); } diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java index 4d73e1cfd..b86450b10 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java @@ -123,7 +123,7 @@ public abstract class PlayQueue implements Serializable { * May throw {@link IndexOutOfBoundsException}. * */ public PlayQueueItem getItem(int index) { - if (index >= streams.size() || streams.get(index) == null) return null; + if (index < 0 || index >= streams.size() || streams.get(index) == null) return null; return streams.get(index); } diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java index 30edcec7c..64d263346 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java @@ -16,10 +16,10 @@ public final class PlaylistPlayQueue extends AbstractInfoPlayQueue streams, - final int index) { + final String url, + final String nextPageUrl, + final List streams, + final int index) { super(serviceId, url, nextPageUrl, streams, index); } diff --git a/app/src/main/res/layout/channel_header.xml b/app/src/main/res/layout/channel_header.xml index a817f7f79..7f08e5932 100644 --- a/app/src/main/res/layout/channel_header.xml +++ b/app/src/main/res/layout/channel_header.xml @@ -5,76 +5,138 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/channel_header_layout" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="12dp"> + android:layout_height="wrap_content"> - + android:layout_height="wrap_content"> - + - + + + + + + + + + - - - - + android:layout_marginEnd="2dp" + android:layout_below="@+id/channel_metadata"> + +