-Added shuffle button to background player.

-Extracted MediaSourceManager window size as parameter.
-Removed redundant list manipulation in PlayQueueAdapter.
This commit is contained in:
John Zhen M 2017-10-09 18:20:11 -07:00 committed by John Zhen Mo
parent f1e52b8b92
commit 77979eddde
7 changed files with 100 additions and 43 deletions

View File

@ -84,7 +84,7 @@ public final class BackgroundPlayer extends Service {
//////////////////////////////////////////////////////////////////////////*/
public interface PlayerEventListener {
void onPlaybackUpdate(int state, int repeatMode, PlaybackParameters parameters);
void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters);
void onProgressUpdate(int currentProgress, int duration, int bufferPercent);
void onMetadataUpdate(StreamInfo info);
void onServiceStopped();
@ -340,8 +340,9 @@ public final class BackgroundPlayer extends Service {
}
@Override
public void onRepeatClicked() {
super.onRepeatClicked();
public void onShuffleClicked() {
super.onShuffleClicked();
updatePlayback();
}
@Override
@ -491,7 +492,7 @@ public final class BackgroundPlayer extends Service {
private void updatePlayback() {
if (activityListener != null) {
activityListener.onPlaybackUpdate(currentState, simpleExoPlayer.getRepeatMode(), simpleExoPlayer.getPlaybackParameters());
activityListener.onPlaybackUpdate(currentState, simpleExoPlayer.getRepeatMode(), playQueue.isShuffled(), simpleExoPlayer.getPlaybackParameters());
}
}

View File

@ -72,6 +72,7 @@ public class BackgroundPlayerActivity extends AppCompatActivity
private ImageButton backwardButton;
private ImageButton playPauseButton;
private ImageButton forwardButton;
private ImageButton shuffleButton;
////////////////////////////////////////////////////////////////////////////
// Activity Lifecycle
@ -196,11 +197,13 @@ public class BackgroundPlayerActivity extends AppCompatActivity
backwardButton = rootView.findViewById(R.id.control_backward);
playPauseButton = rootView.findViewById(R.id.control_play_pause);
forwardButton = rootView.findViewById(R.id.control_forward);
shuffleButton = rootView.findViewById(R.id.control_shuffle);
repeatButton.setOnClickListener(this);
backwardButton.setOnClickListener(this);
playPauseButton.setOnClickListener(this);
forwardButton.setOnClickListener(this);
shuffleButton.setOnClickListener(this);
}
private void buildItemPopupMenu(final PlayQueueItem item, final View view) {
@ -298,6 +301,10 @@ public class BackgroundPlayerActivity extends AppCompatActivity
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
private void scrollToSelected() {
itemsList.smoothScrollToPosition(player.playQueue.getIndex());
}
////////////////////////////////////////////////////////////////////////////
// Component On-Click Listener
////////////////////////////////////////////////////////////////////////////
@ -308,10 +315,14 @@ public class BackgroundPlayerActivity extends AppCompatActivity
player.onRepeatClicked();
} else if (view.getId() == backwardButton.getId()) {
player.onPlayPrevious();
scrollToSelected();
} else if (view.getId() == playPauseButton.getId()) {
player.onVideoPlayPause();
scrollToSelected();
} else if (view.getId() == forwardButton.getId()) {
player.onPlayNext();
} else if (view.getId() == shuffleButton.getId()) {
player.onShuffleClicked();
}
}
@ -340,7 +351,7 @@ public class BackgroundPlayerActivity extends AppCompatActivity
////////////////////////////////////////////////////////////////////////////
@Override
public void onPlaybackUpdate(int state, int repeatMode, PlaybackParameters parameters) {
public void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters) {
switch (state) {
case BasePlayer.STATE_PAUSED:
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
@ -355,29 +366,41 @@ public class BackgroundPlayerActivity extends AppCompatActivity
break;
}
int alpha = 255;
int repeatAlpha = 255;
switch (repeatMode) {
case Player.REPEAT_MODE_OFF:
alpha = 77;
repeatAlpha = 77;
break;
case Player.REPEAT_MODE_ONE:
// todo change image
alpha = 168;
repeatAlpha = 168;
break;
case Player.REPEAT_MODE_ALL:
alpha = 255;
repeatAlpha = 255;
break;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
repeatButton.setImageAlpha(alpha);
repeatButton.setImageAlpha(repeatAlpha);
} else {
repeatButton.setAlpha(alpha);
repeatButton.setAlpha(repeatAlpha);
}
int shuffleAlpha = 255;
if (!shuffled) {
shuffleAlpha = 77;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
shuffleButton.setImageAlpha(shuffleAlpha);
} else {
shuffleButton.setAlpha(shuffleAlpha);
}
if (parameters != null) {
final float speed = parameters.speed;
final float pitch = parameters.pitch;
}
scrollToSelected();
}
@Override
@ -401,6 +424,7 @@ public class BackgroundPlayerActivity extends AppCompatActivity
if (info != null) {
metadataTitle.setText(info.name);
metadataArtist.setText(info.uploader_name);
scrollToSelected();
}
}

View File

@ -535,7 +535,7 @@ public abstract class BasePlayer implements Player.EventListener,
}
/*//////////////////////////////////////////////////////////////////////////
// Repeat
// Repeat and shuffle
//////////////////////////////////////////////////////////////////////////*/
public void onRepeatClicked() {
@ -560,6 +560,18 @@ public abstract class BasePlayer implements Player.EventListener,
if (DEBUG) Log.d(TAG, "onRepeatClicked() currentRepeatMode = " + simpleExoPlayer.getRepeatMode());
}
public void onShuffleClicked() {
if (DEBUG) Log.d(TAG, "onShuffleClicked() called");
if (playQueue == null) return;
if (playQueue.isShuffled()) {
playQueue.unshuffle();
} else {
playQueue.shuffle();
}
}
/*//////////////////////////////////////////////////////////////////////////
// ExoPlayer Listener
//////////////////////////////////////////////////////////////////////////*/

View File

@ -12,13 +12,11 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.mediasource.DeferredMediaSource;
import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.events.ErrorEvent;
import org.schabi.newpipe.playlist.events.MoveEvent;
import org.schabi.newpipe.playlist.events.PlayQueueMessage;
import org.schabi.newpipe.playlist.events.RemoveEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers;
@ -29,10 +27,8 @@ import io.reactivex.functions.Consumer;
public class MediaSourceManager implements DeferredMediaSource.Callback {
private final String TAG = "MediaSourceManager@" + Integer.toHexString(hashCode());
// One-side rolling window size for default loading
// Effectively loads WINDOW_SIZE * 2 + 1 streams, must be greater than 0
// todo: inject this parameter, allow user settings perhaps
private static final int WINDOW_SIZE = 1;
// Effectively loads windowSize * 2 + 1 streams, must be greater than 0
private final int windowSize;
private PlaybackListener playbackListener;
private PlayQueue playQueue;
@ -45,8 +41,15 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
public MediaSourceManager(@NonNull final PlaybackListener listener,
@NonNull final PlayQueue playQueue) {
this(listener, playQueue, 1);
}
public MediaSourceManager(@NonNull final PlaybackListener listener,
@NonNull final PlayQueue playQueue,
final int windowSize) {
this.playbackListener = listener;
this.playQueue = playQueue;
this.windowSize = windowSize;
this.syncReactor = new SerialDisposable();
@ -85,7 +88,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
}
/**
* Loads the current playing stream and the streams within its WINDOW_SIZE bound.
* Loads the current playing stream and the streams within its windowSize bound.
*
* Unblocks the player once the item at the current index is loaded.
* */
@ -97,8 +100,8 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
load(currentItem);
// The rest are just for seamless playback
final int leftBound = Math.max(0, currentIndex - WINDOW_SIZE);
final int rightLimit = currentIndex + WINDOW_SIZE + 1;
final int leftBound = Math.max(0, currentIndex - windowSize);
final int rightLimit = currentIndex + windowSize + 1;
final int rightBound = Math.min(playQueue.size(), rightLimit);
final List<PlayQueueItem> items = new ArrayList<>(playQueue.getStreams().subList(leftBound, rightBound));
@ -119,6 +122,10 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
populateSources();
}
public int getWindowSize() {
return windowSize;
}
/*//////////////////////////////////////////////////////////////////////////
// Event Reactor
//////////////////////////////////////////////////////////////////////////*/
@ -188,7 +195,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
//////////////////////////////////////////////////////////////////////////*/
private boolean isPlayQueueReady() {
return playQueue.isComplete() || playQueue.size() - playQueue.getIndex() > WINDOW_SIZE;
return playQueue.isComplete() || playQueue.size() - playQueue.getIndex() > windowSize;
}
private boolean tryBlock() {

View File

@ -222,6 +222,8 @@ public abstract class PlayQueue implements Serializable {
* */
public synchronized void append(final PlayQueueItem... items) {
streams.addAll(Arrays.asList(items));
if (backup != null) backup.addAll(Arrays.asList(items));
broadcast(new AppendEvent(items.length));
}
@ -232,6 +234,8 @@ public abstract class PlayQueue implements Serializable {
* */
public synchronized void append(final Collection<PlayQueueItem> items) {
streams.addAll(items);
if (backup != null) backup.addAll(items);
broadcast(new AppendEvent(items.size()));
}
@ -271,6 +275,10 @@ public abstract class PlayQueue implements Serializable {
}
streams.remove(index);
if (backup != null) {
final int backupIndex = backup.indexOf(getItem(index));
backup.remove(backupIndex);
}
}
public synchronized void move(final int source, final int target) {
@ -300,7 +308,9 @@ public abstract class PlayQueue implements Serializable {
* Will emit a {@link ReorderEvent} in any context.
* */
public synchronized void shuffle() {
backup = new ArrayList<>(streams);
if (backup == null) {
backup = new ArrayList<>(streams);
}
final PlayQueueItem current = getItem();
Collections.shuffle(streams);
queueIndex.set(streams.indexOf(current));

View File

@ -72,18 +72,6 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
playQueueItemBuilder.setOnSelectedListener(listener);
}
public void add(final List<PlayQueueItem> data) {
playQueue.append(data);
}
public void add(final PlayQueueItem... data) {
playQueue.append(data);
}
public void remove(final int index) {
playQueue.remove(index);
}
private void startReactor() {
final Observer<PlayQueueMessage> observer = new Observer<PlayQueueMessage>() {
@Override

View File

@ -126,23 +126,22 @@
android:id="@+id/control_repeat"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_toLeftOf="@+id/control_backward"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginLeft="5dp"
android:background="#00000000"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:src="@drawable/ic_repeat_white"
tools:ignore="ContentDescription" />
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/control_backward"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:layout_marginLeft="5dp"
android:layout_toLeftOf="@+id/control_play_pause"
android:background="#00000000"
android:clickable="true"
@ -157,8 +156,10 @@
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_centerInParent="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_toLeftOf="@+id/control_forward"
android:background="#00000000"
android:padding="2dp"
android:clickable="true"
@ -171,9 +172,9 @@
android:id="@+id/control_forward"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="8dp"
android:layout_marginRight="5dp"
android:layout_toRightOf="@+id/control_play_pause"
android:background="#00000000"
android:clickable="true"
android:focusable="true"
@ -181,6 +182,20 @@
android:scaleType="fitCenter"
android:src="@drawable/ic_action_av_fast_forward"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/control_shuffle"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_toRightOf="@+id/control_forward"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:background="#00000000"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
android:src="@drawable/ic_palette_white_24dp"
tools:ignore="ContentDescription"/>
</RelativeLayout>
</RelativeLayout>