-Changed quality resolution to persist across player.

-Updated ExoPlayer to 2.5.4.
-Expanded button size in main video player play queue.
-Removed Quality event.
-Extracted player error strings to xml.
This commit is contained in:
John Zhen Mo 2017-10-24 21:47:14 -07:00
parent d0e626c6ee
commit 0806344ffb
19 changed files with 210 additions and 142 deletions

View File

@ -66,7 +66,7 @@ dependencies {
compile 'de.hdodenhof:circleimageview:2.1.0' compile 'de.hdodenhof:circleimageview:2.1.0'
compile 'com.github.nirhart:parallaxscroll:1.0' compile 'com.github.nirhart:parallaxscroll:1.0'
compile 'com.nononsenseapps:filepicker:3.0.1' compile 'com.nononsenseapps:filepicker:3.0.1'
compile 'com.google.android.exoplayer:exoplayer:r2.5.3' compile 'com.google.android.exoplayer:exoplayer:r2.5.4'
debugCompile 'com.facebook.stetho:stetho:1.5.0' debugCompile 'com.facebook.stetho:stetho:1.5.0'
debugCompile 'com.facebook.stetho:stetho-urlconnection:1.5.0' debugCompile 'com.facebook.stetho:stetho-urlconnection:1.5.0'

View File

@ -788,14 +788,14 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
((HistoryListener) activity).onVideoPlayed(currentInfo, getSelectedVideoStream()); ((HistoryListener) activity).onVideoPlayed(currentInfo, getSelectedVideoStream());
} }
final PlayQueue playQueue = new SinglePlayQueue(currentInfo, actionBarHandler.getSelectedVideoStream()); final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
final Intent intent; final Intent intent;
if (append) { if (append) {
Toast.makeText(activity, R.string.popup_playing_append, Toast.LENGTH_SHORT).show(); Toast.makeText(activity, R.string.popup_playing_append, Toast.LENGTH_SHORT).show();
intent = NavigationHelper.getPlayerEnqueueIntent(activity, PopupVideoPlayer.class, playQueue); intent = NavigationHelper.getPlayerEnqueueIntent(activity, PopupVideoPlayer.class, playQueue);
} else { } else {
Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
intent = NavigationHelper.getPlayerIntent(activity, PopupVideoPlayer.class, playQueue); intent = NavigationHelper.getPlayerIntent(activity, PopupVideoPlayer.class, playQueue, getSelectedVideoStream().resolution);
} }
activity.startService(intent); activity.startService(intent);
} }
@ -866,8 +866,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|| (Build.VERSION.SDK_INT < 16); || (Build.VERSION.SDK_INT < 16);
if (!useOldPlayer) { if (!useOldPlayer) {
// ExoPlayer // ExoPlayer
final PlayQueue playQueue = new SinglePlayQueue(currentInfo, actionBarHandler.getSelectedVideoStream()); final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
mIntent = NavigationHelper.getPlayerIntent(activity, MainVideoPlayer.class, playQueue); mIntent = NavigationHelper.getPlayerIntent(activity, MainVideoPlayer.class, playQueue, getSelectedVideoStream().resolution);
} else { } else {
// Internal Player // Internal Player
mIntent = new Intent(activity, PlayVideoActivity.class) mIntent = new Intent(activity, PlayVideoActivity.class)

View File

@ -388,13 +388,23 @@ public final class BackgroundPlayer extends Service {
@Override @Override
public void onRecoverableError(Exception exception) { public void onRecoverableError(Exception exception) {
exception.printStackTrace(); exception.printStackTrace();
Toast.makeText(context, "Failed to play this audio", Toast.LENGTH_SHORT).show();
if (errorToast == null) {
errorToast = Toast.makeText(context, R.string.player_audio_failure, Toast.LENGTH_SHORT);
errorToast.show();
}
} }
@Override @Override
public void onUnrecoverableError(Exception exception) { public void onUnrecoverableError(Exception exception) {
exception.printStackTrace(); exception.printStackTrace();
Toast.makeText(context, "Unexpected error occurred", Toast.LENGTH_SHORT).show();
if (errorToast != null) {
errorToast.cancel();
}
errorToast = Toast.makeText(context, R.string.player_unexpected_failure, Toast.LENGTH_SHORT);
errorToast.show();
shutdown(); shutdown();
} }

View File

@ -36,6 +36,7 @@ import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Toast;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.DefaultLoadControl;
@ -125,6 +126,7 @@ public abstract class BasePlayer implements Player.EventListener,
public static final String REPEAT_MODE = "repeat_mode"; public static final String REPEAT_MODE = "repeat_mode";
public static final String PLAYBACK_PITCH = "playback_pitch"; public static final String PLAYBACK_PITCH = "playback_pitch";
public static final String PLAYBACK_SPEED = "playback_speed"; public static final String PLAYBACK_SPEED = "playback_speed";
public static final String PLAYBACK_QUALITY = "playback_quality";
public static final String PLAY_QUEUE = "play_queue"; public static final String PLAY_QUEUE = "play_queue";
public static final String APPEND_ONLY = "append_only"; public static final String APPEND_ONLY = "append_only";
@ -141,6 +143,7 @@ public abstract class BasePlayer implements Player.EventListener,
protected StreamInfo currentInfo; protected StreamInfo currentInfo;
protected PlayQueueItem currentItem; protected PlayQueueItem currentItem;
protected Toast errorToast;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Player // Player
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -659,8 +662,8 @@ public abstract class BasePlayer implements Player.EventListener,
* {@link ExoPlaybackException#TYPE_SOURCE TYPE_SOURCE}: <br><br> * {@link ExoPlaybackException#TYPE_SOURCE TYPE_SOURCE}: <br><br>
* If the current {@link com.google.android.exoplayer2.Timeline.Window window} has * If the current {@link com.google.android.exoplayer2.Timeline.Window window} has
* duration and position greater than 0, then we know the current window is working correctly * duration and position greater than 0, then we know the current window is working correctly
* and the error is produced by transitioning into a bad window, therefore we simply increment * and the error is produced by transitioning into a bad window, therefore we report an error
* the current index. Otherwise, we report an error to the play queue. * to the play queue based on if the current error can be skipped.
* *
* This is done because ExoPlayer reports the source exceptions before window is * This is done because ExoPlayer reports the source exceptions before window is
* transitioned on seamless playback. * transitioned on seamless playback.
@ -668,27 +671,33 @@ public abstract class BasePlayer implements Player.EventListener,
* Because player error causes ExoPlayer to go back to {@link Player#STATE_IDLE STATE_IDLE}, * Because player error causes ExoPlayer to go back to {@link Player#STATE_IDLE STATE_IDLE},
* we reset and prepare the media source again to resume playback.<br><br> * we reset and prepare the media source again to resume playback.<br><br>
* *
* {@link ExoPlaybackException#TYPE_RENDERER TYPE_RENDERER} and
* {@link ExoPlaybackException#TYPE_UNEXPECTED TYPE_UNEXPECTED}: <br><br> * {@link ExoPlaybackException#TYPE_UNEXPECTED TYPE_UNEXPECTED}: <br><br>
* If renderer failed or unexpected exceptions occurred, treat the error as unrecoverable. * If a runtime error occurred, then we can try to recover it by restarting the playback
* after setting the timestamp recovery.
*
* {@link ExoPlaybackException#TYPE_RENDERER TYPE_RENDERER}: <br><br>
* If the renderer failed, treat the error as unrecoverable.
* *
* @see Player.EventListener#onPlayerError(ExoPlaybackException) * @see Player.EventListener#onPlayerError(ExoPlaybackException)
* */ * */
@Override @Override
public void onPlayerError(ExoPlaybackException error) { public void onPlayerError(ExoPlaybackException error) {
if (DEBUG) Log.d(TAG, "onPlayerError() called with: error = [" + error + "]"); if (DEBUG) Log.d(TAG, "onPlayerError() called with: error = [" + error + "]");
if (errorToast != null) {
errorToast.cancel();
errorToast = null;
}
switch (error.type) { switch (error.type) {
case ExoPlaybackException.TYPE_SOURCE: case ExoPlaybackException.TYPE_SOURCE:
if (simpleExoPlayer.getDuration() < 0 || simpleExoPlayer.getCurrentPosition() < 0) { final boolean skippable = simpleExoPlayer.getDuration() >= 0 && simpleExoPlayer.getCurrentPosition() >= 0;
playQueue.error(); playQueue.error(skippable);
onRecoverableError(error); onRecoverableError(error);
} else { break;
playQueue.offsetIndex(+1); case ExoPlaybackException.TYPE_UNEXPECTED:
} onRecoverableError(error);
setRecovery();
playbackManager.reset(); reload();
playbackManager.load();
break; break;
default: default:
onUnrecoverableError(error); onUnrecoverableError(error);
@ -883,6 +892,13 @@ public abstract class BasePlayer implements Player.EventListener,
return pitchFormatter.format(pitch); return pitchFormatter.format(pitch);
} }
protected void reload() {
if (playbackManager != null) {
playbackManager.reset();
playbackManager.load();
}
}
protected void startProgressLoop() { protected void startProgressLoop() {
if (progressUpdateReactor != null) progressUpdateReactor.dispose(); if (progressUpdateReactor != null) progressUpdateReactor.dispose();
progressUpdateReactor = getProgressReactor(); progressUpdateReactor = getProgressReactor();

View File

@ -344,7 +344,8 @@ public final class MainVideoPlayer extends Activity {
this.getPlayQueue(), this.getPlayQueue(),
this.simpleExoPlayer.getRepeatMode(), this.simpleExoPlayer.getRepeatMode(),
this.getPlaybackSpeed(), this.getPlaybackSpeed(),
this.getPlaybackPitch() this.getPlaybackPitch(),
this.getPlaybackQuality()
); );
context.startService(intent); context.startService(intent);
destroyPlayer(); destroyPlayer();
@ -432,13 +433,23 @@ public final class MainVideoPlayer extends Activity {
@Override @Override
public void onRecoverableError(Exception exception) { public void onRecoverableError(Exception exception) {
exception.printStackTrace(); exception.printStackTrace();
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
if (errorToast == null) {
errorToast = Toast.makeText(context, R.string.player_video_failure, Toast.LENGTH_SHORT);
errorToast.show();
}
} }
@Override @Override
public void onUnrecoverableError(Exception exception) { public void onUnrecoverableError(Exception exception) {
exception.printStackTrace(); exception.printStackTrace();
Toast.makeText(context, "Unexpected error occurred", Toast.LENGTH_SHORT).show();
if (errorToast != null) {
errorToast.cancel();
}
errorToast = Toast.makeText(context, R.string.player_unexpected_failure, Toast.LENGTH_SHORT);
errorToast.show();
shutdown(); shutdown();
} }
@ -447,6 +458,12 @@ public final class MainVideoPlayer extends Activity {
return ListHelper.getDefaultResolutionIndex(context, sortedVideos); return ListHelper.getDefaultResolutionIndex(context, sortedVideos);
} }
@Override
protected int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
final String playbackQuality) {
return ListHelper.getDefaultResolutionIndex(context, sortedVideos, playbackQuality);
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// States // States
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/

View File

@ -441,7 +441,8 @@ public final class PopupVideoPlayer extends Service {
this.getPlayQueue(), this.getPlayQueue(),
this.simpleExoPlayer.getRepeatMode(), this.simpleExoPlayer.getRepeatMode(),
this.getPlaybackSpeed(), this.getPlaybackSpeed(),
this.getPlaybackPitch() this.getPlaybackPitch(),
this.getPlaybackQuality()
); );
if (!isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false); if (!isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@ -468,13 +469,23 @@ public final class PopupVideoPlayer extends Service {
@Override @Override
public void onRecoverableError(Exception exception) { public void onRecoverableError(Exception exception) {
exception.printStackTrace(); exception.printStackTrace();
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
if (errorToast == null) {
errorToast = Toast.makeText(context, R.string.player_video_failure, Toast.LENGTH_SHORT);
errorToast.show();
}
} }
@Override @Override
public void onUnrecoverableError(Exception exception) { public void onUnrecoverableError(Exception exception) {
exception.printStackTrace(); exception.printStackTrace();
Toast.makeText(context, "Unexpected error occurred", Toast.LENGTH_SHORT).show();
if (errorToast != null) {
errorToast.cancel();
}
errorToast = Toast.makeText(context, R.string.player_unexpected_failure, Toast.LENGTH_SHORT);
errorToast.show();
shutdown(); shutdown();
} }
@ -503,6 +514,12 @@ public final class PopupVideoPlayer extends Service {
return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos); return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
} }
@Override
protected int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
final String playbackQuality) {
return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos, playbackQuality);
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Activity Event Listener // Activity Event Listener
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/

View File

@ -25,6 +25,7 @@ import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder; import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
@ -48,6 +49,7 @@ import android.widget.TextView;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource; import com.google.android.exoplayer2.source.MergingMediaSource;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
@ -82,15 +84,17 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe"; public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe";
private ArrayList<VideoStream> availableStreams;
private int selectedStreamIndex;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Player // Player
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
private ArrayList<VideoStream> availableStreams;
private int selectedStreamIndex;
protected String playbackQuality;
private boolean startedFromNewPipe = true; private boolean startedFromNewPipe = true;
protected boolean wasPlaying = false; protected boolean wasPlaying = false;
@ -125,7 +129,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
private Handler controlsVisibilityHandler = new Handler(); private Handler controlsVisibilityHandler = new Handler();
private boolean isSomePopupMenuVisible = false; private boolean isSomePopupMenuVisible = false;
private boolean qualityChanged = false;
private int qualityPopupMenuGroupId = 69; private int qualityPopupMenuGroupId = 69;
private PopupMenu qualityPopupMenu; private PopupMenu qualityPopupMenu;
@ -197,6 +200,17 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
} }
} }
@Override
public void handleIntent(final Intent intent) {
if (intent == null) return;
if (intent.hasExtra(PLAYBACK_QUALITY)) {
setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY));
}
super.handleIntent(intent);
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// UI Builders // UI Builders
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -232,6 +246,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
protected abstract int getDefaultResolutionIndex(final List<VideoStream> sortedVideos); protected abstract int getDefaultResolutionIndex(final List<VideoStream> sortedVideos);
protected abstract int getOverrideResolutionIndex(final List<VideoStream> sortedVideos, final String playbackQuality);
@Override @Override
public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) { public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
super.sync(item, info); super.sync(item, info);
@ -241,11 +257,10 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (info != null) { if (info != null) {
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false); final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);
availableStreams = new ArrayList<>(videos); availableStreams = new ArrayList<>(videos);
final int qualityIndex = item.getQualityIndex(); if (playbackQuality == null) {
if (qualityIndex == PlayQueueItem.DEFAULT_QUALITY) {
selectedStreamIndex = getDefaultResolutionIndex(videos); selectedStreamIndex = getDefaultResolutionIndex(videos);
} else { } else {
selectedStreamIndex = qualityIndex; selectedStreamIndex = getOverrideResolutionIndex(videos, getPlaybackQuality());
} }
buildQualityMenu(); buildQualityMenu();
@ -255,17 +270,17 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
} }
} }
@Override
public MediaSource sourceOf(final PlayQueueItem item, 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); final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false);
final int sortedStreamsIndex = item.getQualityIndex();
if (videos.isEmpty() || sortedStreamsIndex >= videos.size()) return null;
final VideoStream video; final VideoStream video;
if (sortedStreamsIndex == PlayQueueItem.DEFAULT_QUALITY) { if (playbackQuality == null) {
final int index = getDefaultResolutionIndex(videos); final int index = getDefaultResolutionIndex(videos);
video = videos.get(index); video = videos.get(index);
} else { } else {
video = videos.get(sortedStreamsIndex); final int index = getOverrideResolutionIndex(videos, getPlaybackQuality());
video = videos.get(index);
} }
final MediaSource streamSource = buildMediaSource(video.url, MediaFormat.getSuffixById(video.format)); final MediaSource streamSource = buildMediaSource(video.url, MediaFormat.getSuffixById(video.format));
@ -455,10 +470,15 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]"); Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
if (qualityPopupMenuGroupId == menuItem.getGroupId()) { if (qualityPopupMenuGroupId == menuItem.getGroupId()) {
if (selectedStreamIndex == menuItem.getItemId()) return true; final int menuItemIndex = menuItem.getItemId();
if (selectedStreamIndex == menuItemIndex ||
availableStreams == null || availableStreams.size() <= menuItemIndex) return true;
final String oldResolution = getPlaybackQuality();
final String newResolution = availableStreams.get(menuItemIndex).resolution;
setRecovery(); setRecovery();
playQueue.setQuality(playQueue.getIndex(), menuItem.getItemId()); setPlaybackQuality(newResolution);
reload();
qualityTextView.setText(menuItem.getTitle()); qualityTextView.setText(menuItem.getTitle());
return true; return true;
@ -489,8 +509,9 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
isSomePopupMenuVisible = true; isSomePopupMenuVisible = true;
showControls(300); showControls(300);
VideoStream videoStream = getSelectedVideoStream(); final VideoStream videoStream = getSelectedVideoStream();
qualityTextView.setText(MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution); final String qualityText = MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution;
qualityTextView.setText(qualityText);
wasPlaying = simpleExoPlayer.getPlayWhenReady(); wasPlaying = simpleExoPlayer.getPlayWhenReady();
} }
@ -648,6 +669,14 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
// Getters and Setters // Getters and Setters
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public void setPlaybackQuality(final String quality) {
this.playbackQuality = quality;
}
public String getPlaybackQuality() {
return playbackQuality;
}
public AspectRatioFrameLayout getAspectRatioFrameLayout() { public AspectRatioFrameLayout getAspectRatioFrameLayout() {
return aspectRatioFrameLayout; return aspectRatioFrameLayout;
} }

View File

@ -158,8 +158,8 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
// why no pattern matching in Java =( // why no pattern matching in Java =(
switch (event.type()) { switch (event.type()) {
case INIT: case INIT:
case QUALITY:
case REORDER: case REORDER:
case ERROR:
reset(); reset();
break; break;
case APPEND: case APPEND:
@ -177,7 +177,6 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
final MoveEvent moveEvent = (MoveEvent) event; final MoveEvent moveEvent = (MoveEvent) event;
move(moveEvent.getFromIndex(), moveEvent.getToIndex()); move(moveEvent.getFromIndex(), moveEvent.getToIndex());
break; break;
case ERROR:
case RECOVERY: case RECOVERY:
default: default:
break; break;

View File

@ -14,7 +14,6 @@ import org.schabi.newpipe.playlist.events.RecoveryEvent;
import org.schabi.newpipe.playlist.events.RemoveEvent; import org.schabi.newpipe.playlist.events.RemoveEvent;
import org.schabi.newpipe.playlist.events.ReorderEvent; import org.schabi.newpipe.playlist.events.ReorderEvent;
import org.schabi.newpipe.playlist.events.SelectEvent; import org.schabi.newpipe.playlist.events.SelectEvent;
import org.schabi.newpipe.playlist.events.QualityEvent;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
@ -247,15 +246,22 @@ public abstract class PlayQueue implements Serializable {
} }
/** /**
* Report an exception for the item at the current index in order to remove it. * Report an exception for the item at the current index in order and the course of action:
* if the error can be skipped or the current item should be removed.
* *
* This is done as a separate event as the underlying manager may have * This is done as a separate event as the underlying manager may have
* different implementation regarding exceptions. * different implementation regarding exceptions.
* */ * */
public synchronized void error() { public synchronized void error(final boolean skippable) {
final int index = getIndex(); final int index = getIndex();
if (skippable) {
queueIndex.incrementAndGet();
} else {
removeInternal(index); removeInternal(index);
broadcast(new ErrorEvent(index)); }
broadcast(new ErrorEvent(index, skippable));
} }
private synchronized void removeInternal(final int index) { private synchronized void removeInternal(final int index) {
@ -301,21 +307,6 @@ public abstract class PlayQueue implements Serializable {
broadcast(new MoveEvent(source, target)); 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. * Sets the recovery record of the item at the index.
* *

View File

@ -102,7 +102,6 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
private void onPlayQueueChanged(final PlayQueueEvent message) { private void onPlayQueueChanged(final PlayQueueEvent message) {
switch (message.type()) { switch (message.type()) {
case RECOVERY: case RECOVERY:
case QUALITY:
// Do nothing. // Do nothing.
break; break;
case SELECT: case SELECT:
@ -116,12 +115,14 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
break; break;
case ERROR: case ERROR:
final ErrorEvent errorEvent = (ErrorEvent) message; final ErrorEvent errorEvent = (ErrorEvent) message;
notifyItemRangeRemoved(errorEvent.index(), 1); if (!errorEvent.isSkippable()) {
notifyItemRemoved(errorEvent.index());
}
notifyItemChanged(errorEvent.index()); notifyItemChanged(errorEvent.index());
break; break;
case REMOVE: case REMOVE:
final RemoveEvent removeEvent = (RemoveEvent) message; final RemoveEvent removeEvent = (RemoveEvent) message;
notifyItemRangeRemoved(removeEvent.index(), 1); notifyItemRemoved(removeEvent.index());
notifyItemChanged(removeEvent.index()); notifyItemChanged(removeEvent.index());
break; break;
case MOVE: case MOVE:

View File

@ -15,7 +15,6 @@ import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
public class PlayQueueItem implements Serializable { public class PlayQueueItem implements Serializable {
final public static int DEFAULT_QUALITY = Integer.MIN_VALUE;
final public static long RECOVERY_UNSET = Long.MIN_VALUE; final public static long RECOVERY_UNSET = Long.MIN_VALUE;
final private String title; final private String title;
@ -25,7 +24,6 @@ public class PlayQueueItem implements Serializable {
final private String thumbnailUrl; final private String thumbnailUrl;
final private String uploader; final private String uploader;
private int qualityIndex;
private long recoveryPosition; private long recoveryPosition;
private Throwable error; private Throwable error;
@ -36,11 +34,6 @@ public class PlayQueueItem implements Serializable {
this.stream = Single.just(info); this.stream = Single.just(info);
} }
PlayQueueItem(@NonNull final StreamInfo info, final int qualityIndex) {
this(info);
this.qualityIndex = qualityIndex;
}
PlayQueueItem(@NonNull final StreamInfoItem item) { PlayQueueItem(@NonNull final StreamInfoItem item) {
this(item.name, item.url, item.service_id, item.duration, item.thumbnail_url, item.uploader_name); this(item.name, item.url, item.service_id, item.duration, item.thumbnail_url, item.uploader_name);
} }
@ -54,7 +47,6 @@ public class PlayQueueItem implements Serializable {
this.thumbnailUrl = thumbnailUrl; this.thumbnailUrl = thumbnailUrl;
this.uploader = uploader; this.uploader = uploader;
this.qualityIndex = DEFAULT_QUALITY;
this.recoveryPosition = RECOVERY_UNSET; this.recoveryPosition = RECOVERY_UNSET;
} }
@ -86,10 +78,6 @@ public class PlayQueueItem implements Serializable {
return uploader; return uploader;
} }
public int getQualityIndex() {
return qualityIndex;
}
public long getRecoveryPosition() { public long getRecoveryPosition() {
return recoveryPosition; return recoveryPosition;
} }
@ -123,10 +111,6 @@ public class PlayQueueItem implements Serializable {
// Item States, keep external access out // Item States, keep external access out
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
/*package-private*/ void setQualityIndex(final int qualityIndex) {
this.qualityIndex = qualityIndex;
}
/*package-private*/ void setRecoveryPosition(final long recoveryPosition) { /*package-private*/ void setRecoveryPosition(final long recoveryPosition) {
this.recoveryPosition = recoveryPosition; this.recoveryPosition = recoveryPosition;
} }

View File

@ -9,10 +9,6 @@ public final class SinglePlayQueue extends PlayQueue {
super(0, Collections.singletonList(new PlayQueueItem(info))); super(0, Collections.singletonList(new PlayQueueItem(info)));
} }
public SinglePlayQueue(final StreamInfo info, final int qualityIndex) {
super(0, Collections.singletonList(new PlayQueueItem(info, qualityIndex)));
}
@Override @Override
public boolean isComplete() { public boolean isComplete() {
return true; return true;

View File

@ -3,17 +3,23 @@ package org.schabi.newpipe.playlist.events;
public class ErrorEvent implements PlayQueueEvent { public class ErrorEvent implements PlayQueueEvent {
final private int index; final private int index;
final private boolean skippable;
@Override @Override
public PlayQueueEventType type() { public PlayQueueEventType type() {
return PlayQueueEventType.ERROR; return PlayQueueEventType.ERROR;
} }
public ErrorEvent(final int index) { public ErrorEvent(final int index, final boolean skippable) {
this.index = index; this.index = index;
this.skippable = skippable;
} }
public int index() { public int index() {
return index; return index;
} }
public boolean isSkippable() {
return skippable;
}
} }

View File

@ -18,9 +18,6 @@ public enum PlayQueueEventType {
// sent when queue is shuffled // sent when queue is shuffled
REORDER, REORDER,
// sent when quality index is set on a stream
QUALITY,
// sent when recovery record is set on a stream // sent when recovery record is set on a stream
RECOVERY, RECOVERY,

View File

@ -1,31 +0,0 @@
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;
}
}

View File

@ -56,6 +56,13 @@ public final class ListHelper {
if (defaultPreferences == null) return 0; if (defaultPreferences == null) return 0;
String defaultResolution = defaultPreferences.getString(context.getString(R.string.default_resolution_key), context.getString(R.string.default_resolution_value)); String defaultResolution = defaultPreferences.getString(context.getString(R.string.default_resolution_key), context.getString(R.string.default_resolution_value));
return getDefaultResolutionIndex(context, videoStreams, defaultResolution);
}
/**
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
*/
public static int getDefaultResolutionIndex(Context context, List<VideoStream> videoStreams, String defaultResolution) {
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams); return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
} }
@ -67,6 +74,13 @@ public final class ListHelper {
if (defaultPreferences == null) return 0; if (defaultPreferences == null) return 0;
String defaultResolution = defaultPreferences.getString(context.getString(R.string.default_popup_resolution_key), context.getString(R.string.default_popup_resolution_value)); String defaultResolution = defaultPreferences.getString(context.getString(R.string.default_popup_resolution_key), context.getString(R.string.default_popup_resolution_value));
return getPopupDefaultResolutionIndex(context, videoStreams, defaultResolution);
}
/**
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
*/
public static int getPopupDefaultResolutionIndex(Context context, List<VideoStream> videoStreams, String defaultResolution) {
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams); return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
} }

View File

@ -39,21 +39,19 @@ public class NavigationHelper {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public static Intent getPlayerIntent(final Context context, public static Intent getPlayerIntent(final Context context,
final Class targetClazz, final Class targetClazz,
final PlayQueue playQueue) { final PlayQueue playQueue,
return new Intent(context, targetClazz) final String quality) {
Intent intent = new Intent(context, targetClazz)
.putExtra(VideoPlayer.PLAY_QUEUE, playQueue); .putExtra(VideoPlayer.PLAY_QUEUE, playQueue);
if (quality != null) intent.putExtra(VideoPlayer.PLAYBACK_QUALITY, quality);
return intent;
} }
public static Intent getPlayerIntent(final Context context, public static Intent getPlayerIntent(final Context context,
final Class targetClazz, final Class targetClazz,
final PlayQueue playQueue, final PlayQueue playQueue) {
final int repeatMode, return getPlayerIntent(context, targetClazz, playQueue, null);
final float playbackSpeed,
final float playbackPitch) {
return getPlayerIntent(context, targetClazz, playQueue)
.putExtra(BasePlayer.REPEAT_MODE, repeatMode)
.putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed)
.putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch);
} }
public static Intent getPlayerEnqueueIntent(final Context context, public static Intent getPlayerEnqueueIntent(final Context context,
@ -63,6 +61,19 @@ public class NavigationHelper {
.putExtra(BasePlayer.APPEND_ONLY, true); .putExtra(BasePlayer.APPEND_ONLY, true);
} }
public static Intent getPlayerIntent(final Context context,
final Class targetClazz,
final PlayQueue playQueue,
final int repeatMode,
final float playbackSpeed,
final float playbackPitch,
final String playbackQuality) {
return getPlayerIntent(context, targetClazz, playQueue, playbackQuality)
.putExtra(BasePlayer.REPEAT_MODE, repeatMode)
.putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed)
.putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch);
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Through FragmentManager // Through FragmentManager
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/

View File

@ -57,13 +57,14 @@
<ImageButton <ImageButton
android:id="@+id/playQueueClose" android:id="@+id/playQueueClose"
android:layout_width="25dp" android:layout_width="50dp"
android:layout_height="25dp" android:layout_height="50dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_marginRight="40dp" android:layout_marginRight="40dp"
android:layout_marginEnd="40dp" android:layout_marginEnd="40dp"
android:padding="10dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:scaleType="fitXY" android:scaleType="fitXY"
@ -73,13 +74,14 @@
<ImageButton <ImageButton
android:id="@+id/repeatButton" android:id="@+id/repeatButton"
android:layout_width="25dp" android:layout_width="50dp"
android:layout_height="25dp" android:layout_height="50dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_marginLeft="40dp" android:layout_marginLeft="40dp"
android:layout_marginStart="40dp" android:layout_marginStart="40dp"
android:padding="10dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:scaleType="fitXY" android:scaleType="fitXY"
@ -89,12 +91,11 @@
<ImageButton <ImageButton
android:id="@+id/shuffleButton" android:id="@+id/shuffleButton"
android:layout_width="25dp" android:layout_width="50dp"
android:layout_height="25dp" android:layout_height="50dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_toRightOf="@id/repeatButton" android:layout_toRightOf="@id/repeatButton"
android:layout_marginLeft="15dp" android:padding="10dp"
android:layout_marginStart="15dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:scaleType="fitXY" android:scaleType="fitXY"
@ -301,7 +302,9 @@
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="100dp" android:layout_height="100dp"
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:background="#00000000" android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitXY" android:scaleType="fitXY"
android:src="@drawable/ic_pause_white" android:src="@drawable/ic_pause_white"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
@ -315,7 +318,9 @@
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:layout_toLeftOf="@id/playPauseButton" android:layout_toLeftOf="@id/playPauseButton"
android:layout_toStartOf="@id/playPauseButton" android:layout_toStartOf="@id/playPauseButton"
android:background="#00000000" android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitXY" android:scaleType="fitXY"
android:src="@drawable/exo_controls_previous" android:src="@drawable/exo_controls_previous"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
@ -329,7 +334,9 @@
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:layout_toRightOf="@id/playPauseButton" android:layout_toRightOf="@id/playPauseButton"
android:layout_toEndOf="@id/playPauseButton" android:layout_toEndOf="@id/playPauseButton"
android:background="#00000000" android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitXY" android:scaleType="fitXY"
android:src="@drawable/exo_controls_next" android:src="@drawable/exo_controls_next"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>

View File

@ -136,6 +136,10 @@
<string name="could_not_get_stream">Could not get any stream</string> <string name="could_not_get_stream">Could not get any stream</string>
<string name="could_not_load_image">Could not load image</string> <string name="could_not_load_image">Could not load image</string>
<string name="app_ui_crash">App/UI crashed</string> <string name="app_ui_crash">App/UI crashed</string>
<string name="player_video_failure">Failed to play this video</string>
<string name="player_audio_failure">Failed to play this audio</string>
<string name="player_unexpected_failure">Unexpected player error occurred</string>
<!-- error activity --> <!-- error activity -->
<string name="sorry_string">Sorry, that should not have happened.</string> <string name="sorry_string">Sorry, that should not have happened.</string>
<string name="guru_meditation" translatable="false">Guru Meditation.</string> <string name="guru_meditation" translatable="false">Guru Meditation.</string>