-Reverted manual track selection from exoplayer track selector.
-Added quality record to play queue items. -Added quality and recovery record play queue events. -Added landscape view for ServicePlayerActivity. -Moved repeat and shuffle button to play queue panel in main video player. -Fixed potential NPE in MediaSourceManager by no longer nulling play queue on dispose. -Renamed PlayQueueEvent to PlayQueueEventType. -Renamed PlayQueueMessage to PlayQueueEvent.
This commit is contained in:
parent
4553850412
commit
9068247856
|
@ -788,16 +788,14 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
((HistoryListener) activity).onVideoPlayed(currentInfo, getSelectedVideoStream());
|
||||
}
|
||||
|
||||
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
|
||||
final VideoStream candidate = sortedStreamVideosList.get(actionBarHandler.getSelectedVideoStream());
|
||||
|
||||
final PlayQueue playQueue = new SinglePlayQueue(currentInfo, actionBarHandler.getSelectedVideoStream());
|
||||
final Intent intent;
|
||||
if (append) {
|
||||
Toast.makeText(activity, R.string.popup_playing_append, Toast.LENGTH_SHORT).show();
|
||||
intent = NavigationHelper.getPlayerIntent(activity, PopupVideoPlayer.class, playQueue, true);
|
||||
intent = NavigationHelper.getPlayerEnqueueIntent(activity, PopupVideoPlayer.class, playQueue);
|
||||
} else {
|
||||
Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
|
||||
intent = NavigationHelper.getPlayerIntent(activity, PopupVideoPlayer.class, playQueue, Localization.resolutionOf(candidate.resolution));
|
||||
intent = NavigationHelper.getPlayerIntent(activity, PopupVideoPlayer.class, playQueue);
|
||||
}
|
||||
activity.startService(intent);
|
||||
}
|
||||
|
@ -819,10 +817,11 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
|
||||
private void openNormalBackgroundPlayer(final boolean append) {
|
||||
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
|
||||
activity.startService(NavigationHelper.getPlayerIntent(activity, BackgroundPlayer.class, playQueue, append));
|
||||
if (append) {
|
||||
activity.startService(NavigationHelper.getPlayerEnqueueIntent(activity, BackgroundPlayer.class, playQueue));
|
||||
Toast.makeText(activity, R.string.background_player_append, Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
activity.startService(NavigationHelper.getPlayerIntent(activity, BackgroundPlayer.class, playQueue));
|
||||
Toast.makeText(activity, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
@ -867,9 +866,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
|||
|| (Build.VERSION.SDK_INT < 16);
|
||||
if (!useOldPlayer) {
|
||||
// ExoPlayer
|
||||
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
|
||||
final VideoStream candidate = sortedStreamVideosList.get(actionBarHandler.getSelectedVideoStream());
|
||||
mIntent = NavigationHelper.getPlayerIntent(activity, MainVideoPlayer.class, playQueue, Localization.resolutionOf(candidate.resolution));
|
||||
final PlayQueue playQueue = new SinglePlayQueue(currentInfo, actionBarHandler.getSelectedVideoStream());
|
||||
mIntent = NavigationHelper.getPlayerIntent(activity, MainVideoPlayer.class, playQueue);
|
||||
} else {
|
||||
// Internal Player
|
||||
mIntent = new Intent(activity, PlayVideoActivity.class)
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package org.schabi.newpipe.fragments.list.playlist;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
@ -14,6 +16,7 @@ import android.view.ViewGroup;
|
|||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
|
@ -29,6 +32,7 @@ import org.schabi.newpipe.playlist.PlayQueue;
|
|||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
|
||||
import io.reactivex.Single;
|
||||
|
||||
|
@ -162,6 +166,13 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||
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;
|
||||
}
|
||||
activity.startService(buildPlaylistIntent(PopupVideoPlayer.class));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -290,7 +290,9 @@ public final class BackgroundPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void postProcess(@NonNull final Intent intent) {
|
||||
public void handleIntent(final Intent intent) {
|
||||
super.handleIntent(intent);
|
||||
|
||||
resetNotification();
|
||||
startForeground(NOTIFICATION_ID, notBuilder.build());
|
||||
|
||||
|
@ -437,7 +439,7 @@ public final class BackgroundPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public MediaSource sourceOf(final StreamInfo info) {
|
||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||
final int index = ListHelper.getDefaultAudioFormat(context, info.audio_streams);
|
||||
if (index < 0) return null;
|
||||
|
||||
|
|
|
@ -122,6 +122,8 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
// Intent
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static final String REPEAT_MODE = "repeat_mode";
|
||||
public static final String PLAYBACK_PITCH = "playback_pitch";
|
||||
public static final String PLAYBACK_SPEED = "playback_speed";
|
||||
public static final String PLAY_QUEUE = "play_queue";
|
||||
public static final String APPEND_ONLY = "append_only";
|
||||
|
@ -234,8 +236,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
});
|
||||
}
|
||||
|
||||
protected abstract void postProcess(@NonNull final Intent intent);
|
||||
|
||||
public void handleIntent(Intent intent) {
|
||||
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
|
||||
if (intent == null) return;
|
||||
|
@ -253,6 +253,7 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
}
|
||||
|
||||
setPlaybackSpeed(intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed()));
|
||||
setPlaybackPitch(intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch()));
|
||||
|
||||
// Re-initialization
|
||||
destroyPlayer();
|
||||
|
@ -262,7 +263,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
|
||||
// Good to go...
|
||||
initPlayback(this, queue);
|
||||
postProcess(intent);
|
||||
}
|
||||
|
||||
protected void initPlayback(@NonNull final PlaybackListener listener, @NonNull final PlayQueue queue) {
|
||||
|
@ -288,7 +288,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
public void onThumbnailReceived(Bitmap thumbnail) {
|
||||
if (DEBUG) Log.d(TAG, "onThumbnailReceived() called with: thumbnail = [" + thumbnail + "]");
|
||||
}
|
||||
|
@ -470,7 +469,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
public static final int STATE_PAUSED_SEEK = 127;
|
||||
public static final int STATE_COMPLETED = 128;
|
||||
|
||||
|
||||
protected int currentState = -1;
|
||||
|
||||
public void changeState(int state) {
|
||||
|
@ -577,15 +575,13 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
// Check if recovering
|
||||
if (isCurrentWindowCorrect && currentSourceItem != null &&
|
||||
currentSourceItem.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
|
||||
/* Recovering with sub-second position may cause a long buffer delay in ExoPlayer,
|
||||
* rounding this position to the nearest second will help alleviate this.*/
|
||||
final long position = currentSourceItem.getRecoveryPosition();
|
||||
|
||||
// todo: figure out exactly why this is the case
|
||||
/* Rounding time to nearest second as certain media cannot guarantee a sub-second seek
|
||||
will complete and the player might get stuck in buffering state forever */
|
||||
final long roundedPos = (currentSourceItem.getRecoveryPosition() / 1000) * 1000;
|
||||
|
||||
if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + " at: " + getTimeString((int)roundedPos));
|
||||
if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + " at: " + getTimeString((int)position));
|
||||
simpleExoPlayer.seekTo(currentSourceItem.getRecoveryPosition());
|
||||
currentSourceItem.resetRecoveryPosition();
|
||||
playQueue.unsetRecovery(currentSourceIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -995,10 +991,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch));
|
||||
}
|
||||
|
||||
public int getCurrentResolutionTarget() {
|
||||
return trackSelector != null ? trackSelector.getParameters().maxVideoHeight : Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
public PlayQueue getPlayQueue() {
|
||||
return playQueue;
|
||||
}
|
||||
|
@ -1024,6 +1016,6 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
if (playQueue.size() <= queuePos) return;
|
||||
|
||||
if (DEBUG) Log.d(TAG, "Setting recovery, queue: " + queuePos + ", pos: " + windowPos);
|
||||
playQueue.getItem(queuePos).setRecoveryPosition(windowPos);
|
||||
playQueue.setRecovery(queuePos, windowPos);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import android.os.Build;
|
|||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.util.Log;
|
||||
|
@ -52,7 +51,6 @@ import org.schabi.newpipe.playlist.PlayQueueItem;
|
|||
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItemHolder;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
@ -208,6 +206,15 @@ public final class MainVideoPlayer extends Activity {
|
|||
}
|
||||
}
|
||||
|
||||
protected void setShuffleButton(final ImageButton shuffleButton, final boolean shuffled) {
|
||||
final int shuffleAlpha = shuffled ? 255 : 77;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
shuffleButton.setImageAlpha(shuffleAlpha);
|
||||
} else {
|
||||
shuffleButton.setAlpha(shuffleAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
|
@ -216,8 +223,9 @@ public final class MainVideoPlayer extends Activity {
|
|||
private TextView channelTextView;
|
||||
private TextView volumeTextView;
|
||||
private TextView brightnessTextView;
|
||||
private ImageButton repeatButton;
|
||||
private ImageButton queueButton;
|
||||
private ImageButton repeatButton;
|
||||
private ImageButton shuffleButton;
|
||||
|
||||
private ImageButton screenRotationButton;
|
||||
private ImageButton playPauseButton;
|
||||
|
@ -242,8 +250,9 @@ public final class MainVideoPlayer extends Activity {
|
|||
this.channelTextView = rootView.findViewById(R.id.channelTextView);
|
||||
this.volumeTextView = rootView.findViewById(R.id.volumeTextView);
|
||||
this.brightnessTextView = rootView.findViewById(R.id.brightnessTextView);
|
||||
this.repeatButton = rootView.findViewById(R.id.repeatButton);
|
||||
this.queueButton = rootView.findViewById(R.id.queueButton);
|
||||
this.repeatButton = rootView.findViewById(R.id.repeatButton);
|
||||
this.shuffleButton = rootView.findViewById(R.id.shuffleButton);
|
||||
|
||||
this.screenRotationButton = rootView.findViewById(R.id.screenRotationButton);
|
||||
this.playPauseButton = rootView.findViewById(R.id.playPauseButton);
|
||||
|
@ -264,18 +273,14 @@ public final class MainVideoPlayer extends Activity {
|
|||
|
||||
queueButton.setOnClickListener(this);
|
||||
repeatButton.setOnClickListener(this);
|
||||
shuffleButton.setOnClickListener(this);
|
||||
|
||||
playPauseButton.setOnClickListener(this);
|
||||
playPreviousButton.setOnClickListener(this);
|
||||
playNextButton.setOnClickListener(this);
|
||||
screenRotationButton.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreferredResolution() {
|
||||
if (sharedPreferences == null || context == null) return Integer.MAX_VALUE;
|
||||
return Localization.resolutionOf(sharedPreferences.getString(context.getString(R.string.default_resolution_key), context.getString(R.string.default_resolution_value)));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// ExoPlayer Video Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -283,7 +288,7 @@ public final class MainVideoPlayer extends Activity {
|
|||
@Override
|
||||
public void onRepeatModeChanged(int i) {
|
||||
super.onRepeatModeChanged(i);
|
||||
setRepeatModeButton(repeatButton, simpleExoPlayer.getRepeatMode());
|
||||
updatePlaybackButtons();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -305,6 +310,12 @@ public final class MainVideoPlayer extends Activity {
|
|||
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShuffleClicked() {
|
||||
super.onShuffleClicked();
|
||||
updatePlaybackButtons();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFullScreenButtonClicked() {
|
||||
super.onFullScreenButtonClicked();
|
||||
|
@ -323,8 +334,9 @@ public final class MainVideoPlayer extends Activity {
|
|||
context,
|
||||
PopupVideoPlayer.class,
|
||||
this.getPlayQueue(),
|
||||
this.getCurrentResolutionTarget(),
|
||||
this.getPlaybackSpeed()
|
||||
this.simpleExoPlayer.getRepeatMode(),
|
||||
this.getPlaybackSpeed(),
|
||||
this.getPlaybackPitch()
|
||||
);
|
||||
context.startService(intent);
|
||||
destroyPlayer();
|
||||
|
@ -336,10 +348,7 @@ public final class MainVideoPlayer extends Activity {
|
|||
@Override
|
||||
public void onClick(View v) {
|
||||
super.onClick(v);
|
||||
if (v.getId() == repeatButton.getId()) {
|
||||
onRepeatClicked();
|
||||
|
||||
} else if (v.getId() == playPauseButton.getId()) {
|
||||
if (v.getId() == playPauseButton.getId()) {
|
||||
onVideoPlayPause();
|
||||
|
||||
} else if (v.getId() == playPreviousButton.getId()) {
|
||||
|
@ -354,6 +363,12 @@ public final class MainVideoPlayer extends Activity {
|
|||
} else if (v.getId() == queueButton.getId()) {
|
||||
onQueueClicked();
|
||||
return;
|
||||
} else if (v.getId() == repeatButton.getId()) {
|
||||
onRepeatClicked();
|
||||
return;
|
||||
} else if (v.getId() == shuffleButton.getId()) {
|
||||
onShuffleClicked();
|
||||
return;
|
||||
}
|
||||
|
||||
if (getCurrentState() != STATE_COMPLETED) {
|
||||
|
@ -371,10 +386,14 @@ public final class MainVideoPlayer extends Activity {
|
|||
|
||||
private void onQueueClicked() {
|
||||
queueVisible = true;
|
||||
buildQueue();
|
||||
hideSystemUi();
|
||||
|
||||
buildQueue();
|
||||
updatePlaybackButtons();
|
||||
|
||||
getControlsRoot().setVisibility(View.INVISIBLE);
|
||||
queueLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
itemsList.smoothScrollToPosition(playQueue.getIndex());
|
||||
}
|
||||
|
||||
|
@ -527,12 +546,20 @@ public final class MainVideoPlayer extends Activity {
|
|||
}, delay);
|
||||
}
|
||||
|
||||
private void updatePlaybackButtons() {
|
||||
if (repeatButton == null || shuffleButton == null ||
|
||||
simpleExoPlayer == null || playQueue == null) return;
|
||||
|
||||
setRepeatModeButton(repeatButton, simpleExoPlayer.getRepeatMode());
|
||||
setShuffleButton(shuffleButton, playQueue.isShuffled());
|
||||
}
|
||||
|
||||
private void buildQueue() {
|
||||
queueLayout = findViewById(R.id.play_queue_control);
|
||||
queueLayout = findViewById(R.id.playQueuePanel);
|
||||
|
||||
itemsListCloseButton = findViewById(R.id.play_queue_close_area);
|
||||
itemsListCloseButton = findViewById(R.id.playQueueClose);
|
||||
|
||||
itemsList = findViewById(R.id.play_queue);
|
||||
itemsList = findViewById(R.id.playQueue);
|
||||
itemsList.setAdapter(playQueueAdapter);
|
||||
itemsList.setClickable(true);
|
||||
itemsList.setLongClickable(true);
|
||||
|
|
|
@ -405,12 +405,6 @@ public final class PopupVideoPlayer extends Service {
|
|||
resizingIndicator = rootView.findViewById(R.id.resizing_indicator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreferredResolution() {
|
||||
if (sharedPreferences == null || context == null) return Integer.MAX_VALUE;
|
||||
return Localization.resolutionOf(sharedPreferences.getString(context.getString(R.string.default_popup_resolution_key), context.getString(R.string.default_popup_resolution_value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
|
@ -443,8 +437,9 @@ public final class PopupVideoPlayer extends Service {
|
|||
context,
|
||||
MainVideoPlayer.class,
|
||||
this.getPlayQueue(),
|
||||
this.getCurrentResolutionTarget(),
|
||||
this.getPlaybackSpeed()
|
||||
this.simpleExoPlayer.getRepeatMode(),
|
||||
this.getPlaybackSpeed(),
|
||||
this.getPlaybackPitch()
|
||||
);
|
||||
if (!isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
|
|
@ -115,6 +115,11 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
bind();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_play_queue, menu);
|
||||
|
@ -164,7 +169,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
serviceBound = false;
|
||||
stopPlayerListener();
|
||||
player = null;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,6 +185,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
player = playerFrom(service);
|
||||
if (player == null || player.playQueue == null || player.playQueueAdapter == null || player.simpleExoPlayer == null) {
|
||||
unbind();
|
||||
finish();
|
||||
} else {
|
||||
buildComponents();
|
||||
startPlayerListener();
|
||||
|
@ -460,6 +465,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
@Override
|
||||
public void onServiceStopped() {
|
||||
unbind();
|
||||
finish();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -25,7 +25,6 @@ import android.animation.ObjectAnimator;
|
|||
import android.animation.PropertyValuesHolder;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
|
@ -47,18 +46,10 @@ import android.widget.SeekBar;
|
|||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
@ -90,7 +81,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe";
|
||||
public static final String MAX_RESOLUTION = "max_resolution";
|
||||
|
||||
private ArrayList<VideoStream> availableStreams;
|
||||
private int selectedStreamIndex;
|
||||
|
@ -101,11 +91,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
|
||||
public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
|
||||
|
||||
private static final TrackSelection.Factory FIXED_FACTORY = new FixedTrackSelection.Factory();
|
||||
private List<TrackGroupInfo> trackGroupInfos;
|
||||
private TrackGroupArray videoTrackGroups;
|
||||
private TrackGroup selectedVideoTrackGroup;
|
||||
|
||||
private boolean startedFromNewPipe = true;
|
||||
protected boolean wasPlaying = false;
|
||||
|
||||
|
@ -130,7 +115,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
private SeekBar playbackSeekBar;
|
||||
private TextView playbackCurrentTime;
|
||||
private TextView playbackEndTime;
|
||||
private TextView playbackSpeed;
|
||||
private TextView playbackSpeedTextView;
|
||||
|
||||
private View topControlsRoot;
|
||||
private TextView qualityTextView;
|
||||
|
@ -173,7 +158,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
this.playbackSeekBar = rootView.findViewById(R.id.playbackSeekBar);
|
||||
this.playbackCurrentTime = rootView.findViewById(R.id.playbackCurrentTime);
|
||||
this.playbackEndTime = rootView.findViewById(R.id.playbackEndTime);
|
||||
this.playbackSpeed = rootView.findViewById(R.id.playbackSpeed);
|
||||
this.playbackSpeedTextView = rootView.findViewById(R.id.playbackSpeed);
|
||||
this.bottomControlsRoot = rootView.findViewById(R.id.bottomControls);
|
||||
this.topControlsRoot = rootView.findViewById(R.id.topControls);
|
||||
this.qualityTextView = rootView.findViewById(R.id.qualityTextView);
|
||||
|
@ -186,7 +171,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
this.playbackSeekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
|
||||
|
||||
this.qualityPopupMenu = new PopupMenu(context, qualityTextView);
|
||||
this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeed);
|
||||
this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeedTextView);
|
||||
|
||||
((ProgressBar) this.loadingPanel.findViewById(R.id.progressBarLoadingPanel)).getIndeterminateDrawable().setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY);
|
||||
|
||||
|
@ -196,7 +181,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
public void initListeners() {
|
||||
super.initListeners();
|
||||
playbackSeekBar.setOnSeekBarChangeListener(this);
|
||||
playbackSpeed.setOnClickListener(this);
|
||||
playbackSpeedTextView.setOnClickListener(this);
|
||||
fullScreenButton.setOnClickListener(this);
|
||||
qualityTextView.setOnClickListener(this);
|
||||
}
|
||||
|
@ -212,78 +197,21 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postProcess(@NonNull final Intent intent) {
|
||||
final int resolutionTarget = intent.getIntExtra(MAX_RESOLUTION, getPreferredResolution());
|
||||
trackSelector.setParameters(
|
||||
// Assume video is horizontal
|
||||
new DefaultTrackSelector.Parameters().withMaxVideoSize(Integer.MAX_VALUE, resolutionTarget)
|
||||
);
|
||||
}
|
||||
|
||||
public abstract int getPreferredResolution();
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// UI Builders
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private final class TrackGroupInfo {
|
||||
final int track;
|
||||
final int group;
|
||||
final Format format;
|
||||
|
||||
TrackGroupInfo(final int track, final int group, final Format format) {
|
||||
this.track = track;
|
||||
this.group = group;
|
||||
this.format = format;
|
||||
}
|
||||
}
|
||||
|
||||
private void buildQualityMenu() {
|
||||
if (qualityPopupMenu == null || videoTrackGroups == null || selectedVideoTrackGroup == null
|
||||
|| availableStreams == null || videoTrackGroups.length != availableStreams.size()) return;
|
||||
public void buildQualityMenu() {
|
||||
if (qualityPopupMenu == null) return;
|
||||
|
||||
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
|
||||
trackGroupInfos = new ArrayList<>();
|
||||
int acc = 0;
|
||||
|
||||
// Each group represent a source in sorted order of how the media source was built
|
||||
for (int groupIndex = 0; groupIndex < videoTrackGroups.length; groupIndex++) {
|
||||
final TrackGroup group = videoTrackGroups.get(groupIndex);
|
||||
final VideoStream stream = availableStreams.get(groupIndex);
|
||||
|
||||
// For each source, there may be one or multiple tracks depending on the source type
|
||||
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
|
||||
final Format format = group.getFormat(trackIndex);
|
||||
final boolean isSetCurrent = selectedVideoTrackGroup.indexOf(format) != -1;
|
||||
|
||||
if (group.length == 1 && videoTrackGroups.length == availableStreams.size()) {
|
||||
// If the source is non-adaptive (extractor source), then we use the resolution contained in the stream
|
||||
if (isSetCurrent) qualityTextView.setText(stream.resolution);
|
||||
|
||||
final String menuItem = MediaFormat.getNameById(stream.format) + " " +
|
||||
stream.resolution + " (" + format.width + "x" + format.height + ")";
|
||||
qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, acc, Menu.NONE, menuItem);
|
||||
} else {
|
||||
// Otherwise, we have an adaptive source, which contains multiple formats and
|
||||
// thus have no inherent quality format
|
||||
if (isSetCurrent) qualityTextView.setText(resolutionStringOf(format));
|
||||
|
||||
final MediaFormat mediaFormat = MediaFormat.getFromMimeType(format.sampleMimeType);
|
||||
final String mediaName = mediaFormat == null ? format.sampleMimeType : mediaFormat.name;
|
||||
|
||||
final String menuItem = mediaName + " " + format.width + "x" + format.height;
|
||||
qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, acc, Menu.NONE, menuItem);
|
||||
for (int i = 0; i < availableStreams.size(); i++) {
|
||||
VideoStream videoStream = availableStreams.get(i);
|
||||
qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution);
|
||||
}
|
||||
|
||||
trackGroupInfos.add(new TrackGroupInfo(trackIndex, groupIndex, format));
|
||||
acc++;
|
||||
}
|
||||
}
|
||||
|
||||
qualityTextView.setText(getSelectedVideoStream().resolution);
|
||||
qualityPopupMenu.setOnMenuItemClickListener(this);
|
||||
qualityPopupMenu.setOnDismissListener(this);
|
||||
qualityTextView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void buildPlaybackSpeedMenu() {
|
||||
|
@ -293,7 +221,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) {
|
||||
playbackSpeedPopupMenu.getMenu().add(playbackSpeedPopupMenuGroupId, i, Menu.NONE, formatSpeed(PLAYBACK_SPEEDS[i]));
|
||||
}
|
||||
playbackSpeed.setText(formatSpeed(getPlaybackSpeed()));
|
||||
playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed()));
|
||||
playbackSpeedPopupMenu.setOnMenuItemClickListener(this);
|
||||
playbackSpeedPopupMenu.setOnDismissListener(this);
|
||||
}
|
||||
|
@ -305,27 +233,46 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
@Override
|
||||
public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
|
||||
super.sync(item, info);
|
||||
qualityTextView.setVisibility(View.GONE);
|
||||
playbackSpeedTextView.setVisibility(View.GONE);
|
||||
|
||||
if (info != null) {
|
||||
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);
|
||||
availableStreams = new ArrayList<>(videos);
|
||||
final int qualityIndex = item.getQualityIndex();
|
||||
if (qualityIndex == PlayQueueItem.DEFAULT_QUALITY) {
|
||||
selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, videos);
|
||||
} else {
|
||||
selectedStreamIndex = qualityIndex;
|
||||
}
|
||||
|
||||
buildPlaybackSpeedMenu();
|
||||
buildQualityMenu();
|
||||
buildPlaybackSpeedMenu();
|
||||
qualityTextView.setVisibility(View.VISIBLE);
|
||||
playbackSpeedTextView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public MediaSource sourceOf(final StreamInfo info) {
|
||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);
|
||||
List<MediaSource> sources = new ArrayList<>();
|
||||
final int sortedStreamsIndex = item.getQualityIndex();
|
||||
if (videos.isEmpty() || sortedStreamsIndex >= videos.size()) return null;
|
||||
|
||||
for (final VideoStream video : videos) {
|
||||
final MediaSource mediaSource = buildMediaSource(video.url, MediaFormat.getSuffixById(video.format));
|
||||
sources.add(mediaSource);
|
||||
final VideoStream video;
|
||||
if (sortedStreamsIndex == PlayQueueItem.DEFAULT_QUALITY) {
|
||||
final int index = ListHelper.getDefaultResolutionIndex(context, videos);
|
||||
video = videos.get(index);
|
||||
} else {
|
||||
video = videos.get(sortedStreamsIndex);
|
||||
}
|
||||
|
||||
return new MergingMediaSource(sources.toArray(new MediaSource[sources.size()]));
|
||||
final MediaSource streamSource = buildMediaSource(video.url, MediaFormat.getSuffixById(video.format));
|
||||
final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams);
|
||||
if (!video.isVideoOnly || audio == null) return streamSource;
|
||||
|
||||
// Merge with audio stream in case if video does not contain audio
|
||||
final MediaSource audioSource = buildMediaSource(audio.url, MediaFormat.getSuffixById(audio.format));
|
||||
return new MergingMediaSource(streamSource, audioSource);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -403,24 +350,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
// ExoPlayer Video Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||
super.onTracksChanged(trackGroups, trackSelections);
|
||||
|
||||
if (trackSelector.getCurrentMappedTrackInfo() == null) return;
|
||||
qualityTextView.setVisibility(View.GONE);
|
||||
|
||||
final int videoRendererIndex = getVideoRendererIndex();
|
||||
if (videoRendererIndex == -1) return;
|
||||
|
||||
videoTrackGroups = trackSelector.getCurrentMappedTrackInfo().getTrackGroups(videoRendererIndex);
|
||||
final TrackSelection trackSelection = trackSelections.get(videoRendererIndex);
|
||||
if (trackSelection != null) {
|
||||
selectedVideoTrackGroup = trackSelection.getTrackGroup();
|
||||
buildQualityMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
||||
if (DEBUG) {
|
||||
|
@ -444,7 +373,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
|
||||
playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
|
||||
playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
|
||||
playbackSpeed.setText(formatSpeed(getPlaybackSpeed()));
|
||||
playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed()));
|
||||
|
||||
super.onPrepared(playWhenReady);
|
||||
}
|
||||
|
@ -510,7 +439,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
onFullScreenButtonClicked();
|
||||
} else if (v.getId() == qualityTextView.getId()) {
|
||||
onQualitySelectorClicked();
|
||||
} else if (v.getId() == playbackSpeed.getId()) {
|
||||
} else if (v.getId() == playbackSpeedTextView.getId()) {
|
||||
onPlaybackSpeedClicked();
|
||||
}
|
||||
}
|
||||
|
@ -524,34 +453,19 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
|
||||
|
||||
if (qualityPopupMenuGroupId == menuItem.getGroupId()) {
|
||||
final int itemId = menuItem.getItemId();
|
||||
final TrackGroupInfo info = trackGroupInfos.get(itemId);
|
||||
if (selectedStreamIndex == menuItem.getItemId()) return true;
|
||||
|
||||
// Set selected quality as player lifecycle persistent parameters
|
||||
DefaultTrackSelector.Parameters parameters;
|
||||
if (info.format.width > info.format.height) {
|
||||
// Check if video horizontal
|
||||
parameters = new DefaultTrackSelector.Parameters().withMaxVideoSize(Integer.MAX_VALUE, info.format.height);
|
||||
} else {
|
||||
// Or if vertical
|
||||
parameters = new DefaultTrackSelector.Parameters().withMaxVideoSize(info.format.width, Integer.MAX_VALUE);
|
||||
}
|
||||
trackSelector.setParameters(parameters);
|
||||
|
||||
final int videoRendererIndex = getVideoRendererIndex();
|
||||
if (videoRendererIndex != -1) {
|
||||
// Override the selection with the selected quality in case of different frame rate
|
||||
final MappingTrackSelector.SelectionOverride override = new MappingTrackSelector.SelectionOverride(FIXED_FACTORY, info.group, info.track);
|
||||
trackSelector.setSelectionOverride(videoRendererIndex, videoTrackGroups, override);
|
||||
}
|
||||
setRecovery();
|
||||
playQueue.setQuality(playQueue.getIndex(), menuItem.getItemId());
|
||||
|
||||
qualityTextView.setText(menuItem.getTitle());
|
||||
return true;
|
||||
} else if (playbackSpeedPopupMenuGroupId == menuItem.getGroupId()) {
|
||||
int speedIndex = menuItem.getItemId();
|
||||
float speed = PLAYBACK_SPEEDS[speedIndex];
|
||||
|
||||
setPlaybackSpeed(speed);
|
||||
playbackSpeed.setText(formatSpeed(speed));
|
||||
playbackSpeedTextView.setText(formatSpeed(speed));
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -564,6 +478,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
public void onDismiss(PopupMenu menu) {
|
||||
if (DEBUG) Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]");
|
||||
isSomePopupMenuVisible = false;
|
||||
qualityTextView.setText(getSelectedVideoStream().resolution);
|
||||
}
|
||||
|
||||
public void onQualitySelectorClicked() {
|
||||
|
@ -572,6 +487,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
isSomePopupMenuVisible = true;
|
||||
showControls(300);
|
||||
|
||||
VideoStream videoStream = getSelectedVideoStream();
|
||||
qualityTextView.setText(MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution);
|
||||
wasPlaying = simpleExoPlayer.getPlayWhenReady();
|
||||
}
|
||||
|
||||
|
@ -635,11 +552,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
return -1;
|
||||
}
|
||||
|
||||
public String resolutionStringOf(final Format format) {
|
||||
final String frameRate = format.frameRate > 0 ? String.valueOf((int) format.frameRate) : "";
|
||||
return Math.min(format.width, format.height) + "p" + frameRate;
|
||||
}
|
||||
|
||||
public boolean isControlsVisible() {
|
||||
return controlsRoot != null && controlsRoot.getVisibility() == View.VISIBLE;
|
||||
}
|
||||
|
@ -746,10 +658,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
return wasPlaying;
|
||||
}
|
||||
|
||||
public int getQualityPopupMenuGroupId() {
|
||||
return qualityPopupMenuGroupId;
|
||||
}
|
||||
|
||||
public VideoStream getSelectedVideoStream() {
|
||||
return availableStreams.get(selectedStreamIndex);
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ public final class DeferredMediaSource implements MediaSource {
|
|||
* Player-specific {@link com.google.android.exoplayer2.source.MediaSource} resolution
|
||||
* from a given StreamInfo.
|
||||
* */
|
||||
MediaSource sourceOf(final StreamInfo info);
|
||||
MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info);
|
||||
}
|
||||
|
||||
private PlayQueueItem stream;
|
||||
|
@ -102,8 +102,8 @@ public final class DeferredMediaSource implements MediaSource {
|
|||
* called once only.
|
||||
*
|
||||
* If loading fails here, an error will be propagated out and result in an
|
||||
* {@link com.google.android.exoplayer2.ExoPlaybackException ExoPlaybackException}, which is delegated
|
||||
* to the player.
|
||||
* {@link com.google.android.exoplayer2.ExoPlaybackException ExoPlaybackException},
|
||||
* which is delegated to the player.
|
||||
* */
|
||||
public synchronized void load() {
|
||||
if (stream == null) {
|
||||
|
@ -117,7 +117,7 @@ public final class DeferredMediaSource implements MediaSource {
|
|||
final Function<StreamInfo, MediaSource> onReceive = new Function<StreamInfo, MediaSource>() {
|
||||
@Override
|
||||
public MediaSource apply(StreamInfo streamInfo) throws Exception {
|
||||
return onStreamInfoReceived(streamInfo);
|
||||
return onStreamInfoReceived(stream, streamInfo);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -142,17 +142,18 @@ public final class DeferredMediaSource implements MediaSource {
|
|||
.subscribe(onSuccess, onError);
|
||||
}
|
||||
|
||||
private MediaSource onStreamInfoReceived(final StreamInfo streamInfo) throws Exception {
|
||||
private MediaSource onStreamInfoReceived(@NonNull final PlayQueueItem item,
|
||||
@NonNull final StreamInfo info) throws Exception {
|
||||
if (callback == null) {
|
||||
throw new Exception("No available callback for resolving stream info.");
|
||||
}
|
||||
|
||||
final MediaSource mediaSource = callback.sourceOf(streamInfo);
|
||||
final MediaSource mediaSource = callback.sourceOf(item, info);
|
||||
|
||||
if (mediaSource == null) {
|
||||
throw new Exception("Unable to resolve source from stream info. URL: " + stream.getUrl() +
|
||||
", audio count: " + streamInfo.audio_streams.size() +
|
||||
", video count: " + streamInfo.video_only_streams.size() + streamInfo.video_streams.size());
|
||||
", audio count: " + info.audio_streams.size() +
|
||||
", video count: " + info.video_only_streams.size() + info.video_streams.size());
|
||||
}
|
||||
|
||||
return mediaSource;
|
||||
|
|
|
@ -13,7 +13,7 @@ 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.MoveEvent;
|
||||
import org.schabi.newpipe.playlist.events.PlayQueueMessage;
|
||||
import org.schabi.newpipe.playlist.events.PlayQueueEvent;
|
||||
import org.schabi.newpipe.playlist.events.RemoveEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -65,8 +65,8 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public MediaSource sourceOf(StreamInfo info) {
|
||||
return playbackListener.sourceOf(info);
|
||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||
return playbackListener.sourceOf(item, info);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -83,8 +83,6 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
playQueueReactor = null;
|
||||
syncReactor = null;
|
||||
sources = null;
|
||||
playbackListener = null;
|
||||
playQueue = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,8 +128,8 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
// Event Reactor
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private Subscriber<PlayQueueMessage> getReactor() {
|
||||
return new Subscriber<PlayQueueMessage>() {
|
||||
private Subscriber<PlayQueueEvent> getReactor() {
|
||||
return new Subscriber<PlayQueueEvent>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Subscription d) {
|
||||
if (playQueueReactor != null) playQueueReactor.cancel();
|
||||
|
@ -140,7 +138,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@NonNull PlayQueueMessage playQueueMessage) {
|
||||
public void onNext(@NonNull PlayQueueEvent playQueueMessage) {
|
||||
onPlayQueueChanged(playQueueMessage);
|
||||
}
|
||||
|
||||
|
@ -152,7 +150,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
};
|
||||
}
|
||||
|
||||
private void onPlayQueueChanged(final PlayQueueMessage event) {
|
||||
private void onPlayQueueChanged(final PlayQueueEvent event) {
|
||||
if (playQueue.isEmpty()) {
|
||||
playbackListener.shutdown();
|
||||
}
|
||||
|
@ -160,6 +158,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
// why no pattern matching in Java =(
|
||||
switch (event.type()) {
|
||||
case INIT:
|
||||
case QUALITY:
|
||||
case REORDER:
|
||||
reset();
|
||||
break;
|
||||
|
@ -179,6 +178,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
move(moveEvent.getFromIndex(), moveEvent.getToIndex());
|
||||
break;
|
||||
case ERROR:
|
||||
case RECOVERY:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ public interface PlaybackListener {
|
|||
*
|
||||
* May be called at any time.
|
||||
* */
|
||||
MediaSource sourceOf(final StreamInfo info);
|
||||
MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info);
|
||||
|
||||
/**
|
||||
* Called when the play queue can no longer to played or used.
|
||||
|
|
|
@ -9,10 +9,12 @@ import org.schabi.newpipe.playlist.events.AppendEvent;
|
|||
import org.schabi.newpipe.playlist.events.ErrorEvent;
|
||||
import org.schabi.newpipe.playlist.events.InitEvent;
|
||||
import org.schabi.newpipe.playlist.events.MoveEvent;
|
||||
import org.schabi.newpipe.playlist.events.PlayQueueMessage;
|
||||
import org.schabi.newpipe.playlist.events.PlayQueueEvent;
|
||||
import org.schabi.newpipe.playlist.events.RecoveryEvent;
|
||||
import org.schabi.newpipe.playlist.events.RemoveEvent;
|
||||
import org.schabi.newpipe.playlist.events.ReorderEvent;
|
||||
import org.schabi.newpipe.playlist.events.SelectEvent;
|
||||
import org.schabi.newpipe.playlist.events.QualityEvent;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
@ -46,8 +48,8 @@ public abstract class PlayQueue implements Serializable {
|
|||
private ArrayList<PlayQueueItem> streams;
|
||||
private final AtomicInteger queueIndex;
|
||||
|
||||
private transient BehaviorSubject<PlayQueueMessage> eventBroadcast;
|
||||
private transient Flowable<PlayQueueMessage> broadcastReceiver;
|
||||
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
|
||||
private transient Flowable<PlayQueueEvent> broadcastReceiver;
|
||||
private transient Subscription reportingReactor;
|
||||
|
||||
PlayQueue(final int index, final List<PlayQueueItem> startWith) {
|
||||
|
@ -171,7 +173,7 @@ public abstract class PlayQueue implements Serializable {
|
|||
* May be null if the play queue message bus is not initialized.
|
||||
* */
|
||||
@NonNull
|
||||
public Flowable<PlayQueueMessage> getBroadcastReceiver() {
|
||||
public Flowable<PlayQueueEvent> getBroadcastReceiver() {
|
||||
return broadcastReceiver;
|
||||
}
|
||||
|
||||
|
@ -273,6 +275,15 @@ public abstract class PlayQueue implements Serializable {
|
|||
streams.remove(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a queue item at the source index to the target index.
|
||||
*
|
||||
* If the item being moved is the currently playing, then the current playing index is set
|
||||
* to that of the target.
|
||||
* If the moved item is not the currently playing and moves to an index <b>AFTER</b> the
|
||||
* current playing index, then the current playing index is decremented.
|
||||
* Vice versa if the an item after the currently playing is moved <b>BEFORE</b>.
|
||||
* */
|
||||
public synchronized void move(final int source, final int target) {
|
||||
if (source < 0 || target < 0) return;
|
||||
if (source >= streams.size() || target >= streams.size()) return;
|
||||
|
@ -290,6 +301,42 @@ public abstract class PlayQueue implements Serializable {
|
|||
broadcast(new MoveEvent(source, target));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the quality index at the given item index.
|
||||
*
|
||||
* Broadcasts an update event, signalling to all recipients that they should reset.
|
||||
* */
|
||||
public synchronized void setQuality(final int queueIndex, final int qualityIndex) {
|
||||
if (queueIndex < 0 || queueIndex >= streams.size()) return;
|
||||
|
||||
final PlayQueueItem item = streams.get(queueIndex);
|
||||
final int oldQualityIndex = item.getQualityIndex();
|
||||
|
||||
item.setQualityIndex(qualityIndex);
|
||||
broadcast(new QualityEvent(queueIndex, oldQualityIndex, qualityIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the recovery record of the item at the index.
|
||||
*
|
||||
* Broadcasts a recovery event.
|
||||
* */
|
||||
public synchronized void setRecovery(final int index, final long position) {
|
||||
if (index < 0 || index >= streams.size()) return;
|
||||
|
||||
streams.get(index).setRecoveryPosition(position);
|
||||
broadcast(new RecoveryEvent(index, position));
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke the recovery record of the item at the index.
|
||||
*
|
||||
* Broadcasts a recovery event.
|
||||
* */
|
||||
public synchronized void unsetRecovery(final int index) {
|
||||
setRecovery(index, PlayQueueItem.RECOVERY_UNSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffles the current play queue.
|
||||
*
|
||||
|
@ -345,14 +392,14 @@ public abstract class PlayQueue implements Serializable {
|
|||
// Rx Broadcast
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void broadcast(final PlayQueueMessage event) {
|
||||
private void broadcast(final PlayQueueEvent event) {
|
||||
if (eventBroadcast != null) {
|
||||
eventBroadcast.onNext(event);
|
||||
}
|
||||
}
|
||||
|
||||
private Subscriber<PlayQueueMessage> getSelfReporter() {
|
||||
return new Subscriber<PlayQueueMessage>() {
|
||||
private Subscriber<PlayQueueEvent> getSelfReporter() {
|
||||
return new Subscriber<PlayQueueEvent>() {
|
||||
@Override
|
||||
public void onSubscribe(Subscription s) {
|
||||
if (reportingReactor != null) reportingReactor.cancel();
|
||||
|
@ -361,7 +408,7 @@ public abstract class PlayQueue implements Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onNext(PlayQueueMessage event) {
|
||||
public void onNext(PlayQueueEvent event) {
|
||||
Log.d(TAG, "Received broadcast: " + event.type().name() + ". Current index: " + getIndex() + ", play queue length: " + size() + ".");
|
||||
reportingReactor.request(1);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import org.schabi.newpipe.R;
|
|||
import org.schabi.newpipe.playlist.events.AppendEvent;
|
||||
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.PlayQueueEvent;
|
||||
import org.schabi.newpipe.playlist.events.RemoveEvent;
|
||||
import org.schabi.newpipe.playlist.events.SelectEvent;
|
||||
|
||||
|
@ -73,7 +73,7 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
}
|
||||
|
||||
private void startReactor() {
|
||||
final Observer<PlayQueueMessage> observer = new Observer<PlayQueueMessage>() {
|
||||
final Observer<PlayQueueEvent> observer = new Observer<PlayQueueEvent>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
if (playQueueReactor != null) playQueueReactor.dispose();
|
||||
|
@ -81,7 +81,7 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@NonNull PlayQueueMessage playQueueMessage) {
|
||||
public void onNext(@NonNull PlayQueueEvent playQueueMessage) {
|
||||
onPlayQueueChanged(playQueueMessage);
|
||||
}
|
||||
|
||||
|
@ -99,8 +99,12 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
.subscribe(observer);
|
||||
}
|
||||
|
||||
private void onPlayQueueChanged(final PlayQueueMessage message) {
|
||||
private void onPlayQueueChanged(final PlayQueueEvent message) {
|
||||
switch (message.type()) {
|
||||
case RECOVERY:
|
||||
case QUALITY:
|
||||
// Do nothing.
|
||||
break;
|
||||
case SELECT:
|
||||
final SelectEvent selectEvent = (SelectEvent) message;
|
||||
notifyItemChanged(selectEvent.getOldIndex());
|
||||
|
|
|
@ -36,6 +36,11 @@ public class PlayQueueItem implements Serializable {
|
|||
this.stream = Single.just(info);
|
||||
}
|
||||
|
||||
PlayQueueItem(@NonNull final StreamInfo info, final int qualityIndex) {
|
||||
this(info);
|
||||
this.qualityIndex = qualityIndex;
|
||||
}
|
||||
|
||||
PlayQueueItem(@NonNull final StreamInfoItem item) {
|
||||
this(item.name, item.url, item.service_id, item.duration, item.thumbnail_url, item.uploader_name);
|
||||
}
|
||||
|
@ -49,8 +54,8 @@ public class PlayQueueItem implements Serializable {
|
|||
this.thumbnailUrl = thumbnailUrl;
|
||||
this.uploader = uploader;
|
||||
|
||||
resetQualityIndex();
|
||||
resetRecoveryPosition();
|
||||
this.qualityIndex = DEFAULT_QUALITY;
|
||||
this.recoveryPosition = RECOVERY_UNSET;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -71,14 +76,24 @@ public class PlayQueueItem implements Serializable {
|
|||
return duration;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getThumbnailUrl() {
|
||||
return thumbnailUrl;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
public int getQualityIndex() {
|
||||
return qualityIndex;
|
||||
}
|
||||
|
||||
public long getRecoveryPosition() {
|
||||
return recoveryPosition;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Throwable getError() {
|
||||
return error;
|
||||
|
@ -105,30 +120,14 @@ public class PlayQueueItem implements Serializable {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Item States
|
||||
// Item States, keep external access out
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public int getQualityIndex() {
|
||||
return qualityIndex;
|
||||
}
|
||||
|
||||
public long getRecoveryPosition() {
|
||||
return recoveryPosition;
|
||||
}
|
||||
|
||||
public void setQualityIndex(int qualityIndex) {
|
||||
/*package-private*/ void setQualityIndex(final int qualityIndex) {
|
||||
this.qualityIndex = qualityIndex;
|
||||
}
|
||||
|
||||
public void setRecoveryPosition(long recoveryPosition) {
|
||||
/*package-private*/ void setRecoveryPosition(final long recoveryPosition) {
|
||||
this.recoveryPosition = recoveryPosition;
|
||||
}
|
||||
|
||||
public void resetQualityIndex() {
|
||||
this.qualityIndex = DEFAULT_QUALITY;
|
||||
}
|
||||
|
||||
public void resetRecoveryPosition() {
|
||||
this.recoveryPosition = RECOVERY_UNSET;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,10 @@ public final class SinglePlayQueue extends PlayQueue {
|
|||
super(0, Collections.singletonList(new PlayQueueItem(info)));
|
||||
}
|
||||
|
||||
public SinglePlayQueue(final StreamInfo info, final int qualityIndex) {
|
||||
super(0, Collections.singletonList(new PlayQueueItem(info, qualityIndex)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplete() {
|
||||
return true;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
|
||||
public class AppendEvent implements PlayQueueMessage {
|
||||
public class AppendEvent implements PlayQueueEvent {
|
||||
final private int amount;
|
||||
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.APPEND;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.APPEND;
|
||||
}
|
||||
|
||||
public AppendEvent(final int amount) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
|
||||
public class ErrorEvent implements PlayQueueMessage {
|
||||
public class ErrorEvent implements PlayQueueEvent {
|
||||
final private int index;
|
||||
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.ERROR;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.ERROR;
|
||||
}
|
||||
|
||||
public ErrorEvent(final int index) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
public class InitEvent implements PlayQueueMessage {
|
||||
public class InitEvent implements PlayQueueEvent {
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.INIT;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.INIT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
public class MoveEvent implements PlayQueueMessage {
|
||||
public class MoveEvent implements PlayQueueEvent {
|
||||
final private int fromIndex;
|
||||
final private int toIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.MOVE;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.MOVE;
|
||||
}
|
||||
|
||||
public MoveEvent(final int oldIndex, final int newIndex) {
|
||||
|
|
|
@ -1,24 +1,7 @@
|
|||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
public enum PlayQueueEvent {
|
||||
INIT,
|
||||
import java.io.Serializable;
|
||||
|
||||
// sent when the index is changed
|
||||
SELECT,
|
||||
|
||||
// sent when more streams are added to the play queue
|
||||
APPEND,
|
||||
|
||||
// sent when a pending stream is removed from the play queue
|
||||
REMOVE,
|
||||
|
||||
// sent when two streams swap place in the play queue
|
||||
MOVE,
|
||||
|
||||
// sent when queue is shuffled
|
||||
REORDER,
|
||||
|
||||
// sent when the item at index has caused an exception
|
||||
ERROR
|
||||
public interface PlayQueueEvent extends Serializable {
|
||||
PlayQueueEventType type();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
public enum PlayQueueEventType {
|
||||
INIT,
|
||||
|
||||
// sent when the index is changed
|
||||
SELECT,
|
||||
|
||||
// sent when more streams are added to the play queue
|
||||
APPEND,
|
||||
|
||||
// sent when a pending stream is removed from the play queue
|
||||
REMOVE,
|
||||
|
||||
// sent when two streams swap place in the play queue
|
||||
MOVE,
|
||||
|
||||
// sent when queue is shuffled
|
||||
REORDER,
|
||||
|
||||
// sent when quality index is set on a stream
|
||||
QUALITY,
|
||||
|
||||
// sent when recovery record is set on a stream
|
||||
RECOVERY,
|
||||
|
||||
// sent when the item at index has caused an exception
|
||||
ERROR
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface PlayQueueMessage extends Serializable {
|
||||
PlayQueueEvent type();
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
|
||||
public class QualityEvent implements PlayQueueEvent {
|
||||
final private int streamIndex;
|
||||
final private int oldQualityIndex;
|
||||
final private int newQualityIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.QUALITY;
|
||||
}
|
||||
|
||||
public QualityEvent(final int streamIndex, final int oldQualityIndex, final int newQualityIndex) {
|
||||
this.streamIndex = streamIndex;
|
||||
this.oldQualityIndex = oldQualityIndex;
|
||||
this.newQualityIndex = newQualityIndex;
|
||||
}
|
||||
|
||||
public int getStreamIndex() {
|
||||
return streamIndex;
|
||||
}
|
||||
|
||||
public int getOldQualityIndex() {
|
||||
return oldQualityIndex;
|
||||
}
|
||||
|
||||
public int getNewQualityIndex() {
|
||||
return newQualityIndex;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
|
||||
public class RecoveryEvent implements PlayQueueEvent {
|
||||
final private int index;
|
||||
final private long position;
|
||||
|
||||
@Override
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.RECOVERY;
|
||||
}
|
||||
|
||||
public RecoveryEvent(final int index, final long position) {
|
||||
this.index = index;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public long getPosition() {
|
||||
return position;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
|
||||
public class RemoveEvent implements PlayQueueMessage {
|
||||
public class RemoveEvent implements PlayQueueEvent {
|
||||
final private int index;
|
||||
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.REMOVE;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.REMOVE;
|
||||
}
|
||||
|
||||
public RemoveEvent(final int index) {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
public class ReorderEvent implements PlayQueueMessage {
|
||||
public class ReorderEvent implements PlayQueueEvent {
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.REORDER;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.REORDER;
|
||||
}
|
||||
|
||||
public ReorderEvent() {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package org.schabi.newpipe.playlist.events;
|
||||
|
||||
|
||||
public class SelectEvent implements PlayQueueMessage {
|
||||
public class SelectEvent implements PlayQueueEvent {
|
||||
final private int oldIndex;
|
||||
final private int newIndex;
|
||||
|
||||
@Override
|
||||
public PlayQueueEvent type() {
|
||||
return PlayQueueEvent.SELECT;
|
||||
public PlayQueueEventType type() {
|
||||
return PlayQueueEventType.SELECT;
|
||||
}
|
||||
|
||||
public SelectEvent(final int oldIndex, final int newIndex) {
|
||||
|
|
|
@ -152,13 +152,4 @@ public class Localization {
|
|||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public static int resolutionOf(final String resolution) {
|
||||
final String[] candidates = TextUtils.split(resolution, "p");
|
||||
if (candidates.length > 0 && TextUtils.isDigitsOnly(candidates[0])) {
|
||||
return Integer.parseInt(candidates[0]);
|
||||
} else {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,26 +60,20 @@ public class NavigationHelper {
|
|||
public static Intent getPlayerIntent(final Context context,
|
||||
final Class targetClazz,
|
||||
final PlayQueue playQueue,
|
||||
final boolean isAppending) {
|
||||
final int repeatMode,
|
||||
final float playbackSpeed,
|
||||
final float playbackPitch) {
|
||||
return getPlayerIntent(context, targetClazz, playQueue)
|
||||
.putExtra(BasePlayer.APPEND_ONLY, isAppending);
|
||||
.putExtra(BasePlayer.REPEAT_MODE, repeatMode)
|
||||
.putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed)
|
||||
.putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch);
|
||||
}
|
||||
|
||||
public static Intent getPlayerIntent(final Context context,
|
||||
public static Intent getPlayerEnqueueIntent(final Context context,
|
||||
final Class targetClazz,
|
||||
final PlayQueue playQueue,
|
||||
final int maxResolution) {
|
||||
final PlayQueue playQueue) {
|
||||
return getPlayerIntent(context, targetClazz, playQueue)
|
||||
.putExtra(VideoPlayer.MAX_RESOLUTION, maxResolution);
|
||||
}
|
||||
|
||||
public static Intent getPlayerIntent(final Context context,
|
||||
final Class targetClazz,
|
||||
final PlayQueue playQueue,
|
||||
final int maxResolution,
|
||||
final float playbackSpeed) {
|
||||
return getPlayerIntent(context, targetClazz, playQueue, maxResolution)
|
||||
.putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed);
|
||||
.putExtra(BasePlayer.APPEND_ONLY, true);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="org.schabi.newpipe.player.BackgroundPlayerActivity">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/appbar_padding_top"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:title="@string/app_name"/>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/play_queue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@+id/appbar"
|
||||
android:layout_above="@id/progress_bar"
|
||||
android:layout_toLeftOf="@+id/control_pane"
|
||||
android:layout_toStartOf="@+id/control_pane"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:listitem="@layout/play_queue_item"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_above="@id/progress_bar"
|
||||
android:layout_below="@id/appbar"
|
||||
android:id="@+id/control_pane">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/metadata"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@+id/playback_controls_top"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/song_name"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="3"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:textSize="14sp"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis nec aliquam augue, eget cursus est. Ut id tristique enim, ut scelerisque tellus. Sed ultricies ipsum non mauris ultricies, commodo malesuada velit porta." />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/artist_name"
|
||||
style="@android:style/TextAppearance.StatusBar.EventContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="3"
|
||||
android:textSize="12sp"
|
||||
tools:text="Duis posuere arcu condimentum lobortis mattis." />
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/playback_controls_top"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:paddingTop="10dp"
|
||||
android:layout_above="@+id/playback_controls_bottom"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/control_backward"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:layout_toLeftOf="@+id/control_play_pause"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="2dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/exo_controls_previous"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/control_play_pause"
|
||||
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:background="#00000000"
|
||||
android:padding="2dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_pause_white"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/control_progress_bar"
|
||||
style="?android:attr/progressBarStyleLargeInverse"
|
||||
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:background="#00000000"
|
||||
android:padding="2dp"
|
||||
android:clickable="false"
|
||||
android:scaleType="fitCenter"
|
||||
android:indeterminate="true"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/control_forward"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="5dp"
|
||||
android:layout_toRightOf="@+id/control_play_pause"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="2dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/exo_controls_next"
|
||||
tools:ignore="ContentDescription"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/playback_controls_bottom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:paddingTop="10dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/control_playback_speed"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toLeftOf="@+id/control_repeat"
|
||||
android:gravity="center"
|
||||
android:minWidth="50dp"
|
||||
android:text="1x"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="HardcodedText,RtlHardcoded"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/control_repeat"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_toLeftOf="@+id/anchor"
|
||||
android:layout_centerVertical="true"
|
||||
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"/>
|
||||
|
||||
<View android:layout_width="10dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:id="@+id/anchor"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/control_shuffle"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_toRightOf="@+id/anchor"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="5dp"
|
||||
android:background="#00000000"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_shuffle_white_24dp"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/control_playback_pitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toRightOf="@+id/control_shuffle"
|
||||
android:gravity="center"
|
||||
android:minWidth="50dp"
|
||||
android:text="100%"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="HardcodedText,RtlHardcoded"/>
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:background="@drawable/player_controls_bg"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/current_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:minHeight="40dp"
|
||||
android:text="-:--:--"
|
||||
android:textColor="?attr/colorAccent"
|
||||
tools:ignore="HardcodedText"
|
||||
tools:text="1:06:29"/>
|
||||
|
||||
|
||||
<android.support.v7.widget.AppCompatSeekBar
|
||||
android:id="@+id/seek_bar"
|
||||
style="@style/Widget.AppCompat.SeekBar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:paddingBottom="4dp"
|
||||
android:paddingTop="8dp"
|
||||
tools:progress="25"
|
||||
tools:secondaryProgress="50"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/end_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:text="-:--:--"
|
||||
android:textColor="?attr/colorAccent"
|
||||
tools:ignore="HardcodedText"
|
||||
tools:text="1:23:49"/>
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
|
@ -43,26 +43,72 @@
|
|||
tools:visibility="visible"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/play_queue_control"
|
||||
android:id="@+id/playQueuePanel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#64000000"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/play_queue_close_area"
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:id="@+id/playQueueControl">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/playQueueClose"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginRight="40dp"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_close_white_24dp"
|
||||
android:background="?android:selectableItemBackground"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/repeatButton"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginLeft="40dp"
|
||||
android:layout_marginStart="40dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/exo_controls_repeat_off"
|
||||
android:background="?android:selectableItemBackground"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/shuffleButton"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toRightOf="@id/repeatButton"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginStart="15dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_shuffle_white_24dp"
|
||||
android:background="?android:selectableItemBackground"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/play_queue"
|
||||
android:id="@+id/playQueue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/play_queue_close_area"
|
||||
android:layout_below="@id/playQueueControl"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:listitem="@layout/play_queue_item"/>
|
||||
|
@ -164,7 +210,7 @@
|
|||
android:layout_height="35dp"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_toLeftOf="@+id/repeatButton"
|
||||
android:layout_toLeftOf="@+id/queueButton"
|
||||
android:background="#00ffffff"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
|
@ -173,21 +219,6 @@
|
|||
android:src="@drawable/ic_screen_rotation_white"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/repeatButton"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_toLeftOf="@+id/queueButton"
|
||||
android:background="#00ffffff"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/exo_controls_repeat_off"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/queueButton"
|
||||
android:layout_width="30dp"
|
||||
|
@ -403,12 +434,4 @@
|
|||
tools:visibility="visible"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/video_playlist"
|
||||
android:layout_width="480dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:background="#64000000"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</RelativeLayout>
|
|
@ -163,7 +163,7 @@
|
|||
android:focusable="true"
|
||||
android:padding="2dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_action_av_fast_rewind"
|
||||
android:src="@drawable/exo_controls_previous"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
|
@ -212,7 +212,7 @@
|
|||
android:focusable="true"
|
||||
android:padding="2dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_action_av_fast_forward"
|
||||
android:src="@drawable/exo_controls_next"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<ImageButton
|
||||
|
|
Loading…
Reference in New Issue