-Improved DeferredMediaSource to build source on IO thread.
-Improved exception handling for player.
This commit is contained in:
parent
eebd83d6ac
commit
f5b5982e1c
5 changed files with 101 additions and 38 deletions
|
@ -401,11 +401,18 @@ public final class BackgroundPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
public void onRecoverableError(Exception exception) {
|
||||
exception.printStackTrace();
|
||||
Toast.makeText(context, "Failed to play this audio", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnrecoverableError(Exception exception) {
|
||||
exception.printStackTrace();
|
||||
Toast.makeText(context, "Unexpected error occurred", Toast.LENGTH_SHORT).show();
|
||||
shutdown();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// ExoPlayer Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
|
@ -661,25 +661,48 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the exceptions produced by {@link com.google.android.exoplayer2.ExoPlayer ExoPlayer}.
|
||||
* There are multiple types of errors: <br><br>
|
||||
*
|
||||
* {@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.
|
||||
*
|
||||
* This is done because ExoPlayer reports the source exceptions before window is
|
||||
* transitioned on seamless playback.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @see Player.EventListener#onPlayerError(ExoPlaybackException)
|
||||
* */
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException error) {
|
||||
if (DEBUG) Log.d(TAG, "onPlayerError() called with: error = [" + error + "]");
|
||||
|
||||
// If the current window is seekable, then the error is produced by transitioning into
|
||||
// bad window, therefore we simply increment the current index.
|
||||
// This is done because ExoPlayer reports the exception before window is
|
||||
// transitioned due to seamless playback.
|
||||
if (!simpleExoPlayer.isCurrentWindowSeekable()) {
|
||||
switch (error.type) {
|
||||
case ExoPlaybackException.TYPE_SOURCE:
|
||||
if (simpleExoPlayer.getDuration() < 0 || simpleExoPlayer.getCurrentPosition() < 0) {
|
||||
playQueue.error();
|
||||
onError(error);
|
||||
onRecoverableError(error);
|
||||
} else {
|
||||
playQueue.offsetIndex(+1);
|
||||
}
|
||||
|
||||
// Player error causes ExoPlayer to go back to IDLE state, which requires resetting
|
||||
// preparing a new media source.
|
||||
playbackManager.reset();
|
||||
playbackManager.load();
|
||||
break;
|
||||
default:
|
||||
onUnrecoverableError(error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -752,7 +775,9 @@ public abstract class BasePlayer implements Player.EventListener,
|
|||
// General Player
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public abstract void onError(Exception exception);
|
||||
public abstract void onRecoverableError(Exception exception);
|
||||
|
||||
public abstract void onUnrecoverableError(Exception exception);
|
||||
|
||||
public void onPrepared(boolean playWhenReady) {
|
||||
if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
|
||||
|
|
|
@ -352,11 +352,18 @@ public final class MainVideoPlayer extends Activity {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
public void onRecoverableError(Exception exception) {
|
||||
exception.printStackTrace();
|
||||
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnrecoverableError(Exception exception) {
|
||||
exception.printStackTrace();
|
||||
Toast.makeText(context, "Unexpected error occurred", Toast.LENGTH_SHORT).show();
|
||||
shutdown();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// States
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
|
@ -463,11 +463,18 @@ public final class PopupVideoPlayer extends Service {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
public void onRecoverableError(Exception exception) {
|
||||
exception.printStackTrace();
|
||||
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnrecoverableError(Exception exception) {
|
||||
exception.printStackTrace();
|
||||
Toast.makeText(context, "Unexpected error occurred", Toast.LENGTH_SHORT).show();
|
||||
shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
super.onStopTrackingTouch(seekBar);
|
||||
|
|
|
@ -16,6 +16,7 @@ import java.io.IOException;
|
|||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.functions.Function;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
|
@ -113,10 +114,17 @@ public final class DeferredMediaSource implements MediaSource {
|
|||
|
||||
Log.d(TAG, "Loading: [" + stream.getTitle() + "] with url: " + stream.getUrl());
|
||||
|
||||
final Consumer<StreamInfo> onSuccess = new Consumer<StreamInfo>() {
|
||||
final Function<StreamInfo, MediaSource> onReceive = new Function<StreamInfo, MediaSource>() {
|
||||
@Override
|
||||
public void accept(StreamInfo streamInfo) throws Exception {
|
||||
onStreamInfoReceived(streamInfo);
|
||||
public MediaSource apply(StreamInfo streamInfo) throws Exception {
|
||||
return onStreamInfoReceived(streamInfo);
|
||||
}
|
||||
};
|
||||
|
||||
final Consumer<MediaSource> onSuccess = new Consumer<MediaSource>() {
|
||||
@Override
|
||||
public void accept(MediaSource mediaSource) throws Exception {
|
||||
onMediaSourceReceived(mediaSource);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -128,29 +136,38 @@ public final class DeferredMediaSource implements MediaSource {
|
|||
};
|
||||
|
||||
loader = stream.getStream()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
.map(onReceive)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(onSuccess, onError);
|
||||
}
|
||||
|
||||
private void onStreamInfoReceived(final StreamInfo streamInfo) {
|
||||
private MediaSource onStreamInfoReceived(final StreamInfo streamInfo) throws Exception {
|
||||
if (callback == null) {
|
||||
throw new Exception("No available callback for resolving stream info.");
|
||||
}
|
||||
|
||||
final MediaSource mediaSource = callback.sourceOf(streamInfo);
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
return mediaSource;
|
||||
}
|
||||
|
||||
private void onMediaSourceReceived(final MediaSource mediaSource) throws Exception {
|
||||
if (exoPlayer == null || listener == null || mediaSource == null) {
|
||||
throw new Exception("MediaSource loading failed. URL: " + stream.getUrl());
|
||||
}
|
||||
|
||||
Log.d(TAG, " Loaded: [" + stream.getTitle() + "] with url: " + stream.getUrl());
|
||||
state = STATE_LOADED;
|
||||
|
||||
if (exoPlayer == null || listener == null || streamInfo == null) {
|
||||
error = new Throwable("Stream info loading failed. URL: " + stream.getUrl());
|
||||
return;
|
||||
}
|
||||
|
||||
mediaSource = callback.sourceOf(streamInfo);
|
||||
if (mediaSource == null) {
|
||||
error = new Throwable("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());
|
||||
return;
|
||||
}
|
||||
|
||||
mediaSource.prepareSource(exoPlayer, false, listener);
|
||||
this.mediaSource = mediaSource;
|
||||
this.mediaSource.prepareSource(exoPlayer, false, listener);
|
||||
}
|
||||
|
||||
private void onStreamInfoError(final Throwable throwable) {
|
||||
|
|
Loading…
Reference in a new issue