-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

@ -388,13 +388,23 @@ public final class BackgroundPlayer extends Service {
@Override
public void onRecoverableError(Exception exception) {
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
public void onUnrecoverableError(Exception exception) {
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();
}

View file

@ -36,6 +36,7 @@ import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
@ -125,6 +126,7 @@ public abstract class BasePlayer implements Player.EventListener,
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 PLAYBACK_QUALITY = "playback_quality";
public static final String PLAY_QUEUE = "play_queue";
public static final String APPEND_ONLY = "append_only";
@ -141,6 +143,7 @@ public abstract class BasePlayer implements Player.EventListener,
protected StreamInfo currentInfo;
protected PlayQueueItem currentItem;
protected Toast errorToast;
/*//////////////////////////////////////////////////////////////////////////
// Player
//////////////////////////////////////////////////////////////////////////*/
@ -659,8 +662,8 @@ public abstract class BasePlayer implements Player.EventListener,
* {@link ExoPlaybackException#TYPE_SOURCE TYPE_SOURCE}: <br><br>
* 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
* and the error is produced by transitioning into a bad window, therefore we simply increment
* the current index. Otherwise, we report an error to the play queue.
* and the error is produced by transitioning into a bad window, therefore we report an error
* 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
* 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},
* 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>
* 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)
* */
@Override
public void onPlayerError(ExoPlaybackException error) {
if (DEBUG) Log.d(TAG, "onPlayerError() called with: error = [" + error + "]");
if (errorToast != null) {
errorToast.cancel();
errorToast = null;
}
switch (error.type) {
case ExoPlaybackException.TYPE_SOURCE:
if (simpleExoPlayer.getDuration() < 0 || simpleExoPlayer.getCurrentPosition() < 0) {
playQueue.error();
onRecoverableError(error);
} else {
playQueue.offsetIndex(+1);
}
playbackManager.reset();
playbackManager.load();
final boolean skippable = simpleExoPlayer.getDuration() >= 0 && simpleExoPlayer.getCurrentPosition() >= 0;
playQueue.error(skippable);
onRecoverableError(error);
break;
case ExoPlaybackException.TYPE_UNEXPECTED:
onRecoverableError(error);
setRecovery();
reload();
break;
default:
onUnrecoverableError(error);
@ -883,6 +892,13 @@ public abstract class BasePlayer implements Player.EventListener,
return pitchFormatter.format(pitch);
}
protected void reload() {
if (playbackManager != null) {
playbackManager.reset();
playbackManager.load();
}
}
protected void startProgressLoop() {
if (progressUpdateReactor != null) progressUpdateReactor.dispose();
progressUpdateReactor = getProgressReactor();

View file

@ -344,7 +344,8 @@ public final class MainVideoPlayer extends Activity {
this.getPlayQueue(),
this.simpleExoPlayer.getRepeatMode(),
this.getPlaybackSpeed(),
this.getPlaybackPitch()
this.getPlaybackPitch(),
this.getPlaybackQuality()
);
context.startService(intent);
destroyPlayer();
@ -432,13 +433,23 @@ public final class MainVideoPlayer extends Activity {
@Override
public void onRecoverableError(Exception exception) {
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
public void onUnrecoverableError(Exception exception) {
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();
}
@ -447,6 +458,12 @@ public final class MainVideoPlayer extends Activity {
return ListHelper.getDefaultResolutionIndex(context, sortedVideos);
}
@Override
protected int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
final String playbackQuality) {
return ListHelper.getDefaultResolutionIndex(context, sortedVideos, playbackQuality);
}
/*//////////////////////////////////////////////////////////////////////////
// States
//////////////////////////////////////////////////////////////////////////*/

View file

@ -441,7 +441,8 @@ public final class PopupVideoPlayer extends Service {
this.getPlayQueue(),
this.simpleExoPlayer.getRepeatMode(),
this.getPlaybackSpeed(),
this.getPlaybackPitch()
this.getPlaybackPitch(),
this.getPlaybackQuality()
);
if (!isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@ -468,13 +469,23 @@ public final class PopupVideoPlayer extends Service {
@Override
public void onRecoverableError(Exception exception) {
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
public void onUnrecoverableError(Exception exception) {
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();
}
@ -503,6 +514,12 @@ public final class PopupVideoPlayer extends Service {
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
//////////////////////////////////////////////////////////////////////////*/

View file

@ -25,6 +25,7 @@ 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;
@ -48,6 +49,7 @@ import android.widget.TextView;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
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.MergingMediaSource;
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";
private ArrayList<VideoStream> availableStreams;
private int selectedStreamIndex;
/*//////////////////////////////////////////////////////////////////////////
// Player
//////////////////////////////////////////////////////////////////////////*/
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;
protected boolean wasPlaying = false;
@ -125,7 +129,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
private Handler controlsVisibilityHandler = new Handler();
private boolean isSomePopupMenuVisible = false;
private boolean qualityChanged = false;
private int qualityPopupMenuGroupId = 69;
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
//////////////////////////////////////////////////////////////////////////*/
@ -232,6 +246,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
protected abstract int getDefaultResolutionIndex(final List<VideoStream> sortedVideos);
protected abstract int getOverrideResolutionIndex(final List<VideoStream> sortedVideos, final String playbackQuality);
@Override
public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
super.sync(item, info);
@ -241,11 +257,10 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
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) {
if (playbackQuality == null) {
selectedStreamIndex = getDefaultResolutionIndex(videos);
} else {
selectedStreamIndex = qualityIndex;
selectedStreamIndex = getOverrideResolutionIndex(videos, getPlaybackQuality());
}
buildQualityMenu();
@ -255,17 +270,17 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
}
}
@Override
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 int sortedStreamsIndex = item.getQualityIndex();
if (videos.isEmpty() || sortedStreamsIndex >= videos.size()) return null;
final VideoStream video;
if (sortedStreamsIndex == PlayQueueItem.DEFAULT_QUALITY) {
if (playbackQuality == null) {
final int index = getDefaultResolutionIndex(videos);
video = videos.get(index);
} 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));
@ -455,10 +470,15 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
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();
playQueue.setQuality(playQueue.getIndex(), menuItem.getItemId());
setPlaybackQuality(newResolution);
reload();
qualityTextView.setText(menuItem.getTitle());
return true;
@ -489,8 +509,9 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
isSomePopupMenuVisible = true;
showControls(300);
VideoStream videoStream = getSelectedVideoStream();
qualityTextView.setText(MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution);
final VideoStream videoStream = getSelectedVideoStream();
final String qualityText = MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution;
qualityTextView.setText(qualityText);
wasPlaying = simpleExoPlayer.getPlayWhenReady();
}
@ -648,6 +669,14 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
// Getters and Setters
//////////////////////////////////////////////////////////////////////////*/
public void setPlaybackQuality(final String quality) {
this.playbackQuality = quality;
}
public String getPlaybackQuality() {
return playbackQuality;
}
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
return aspectRatioFrameLayout;
}

View file

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