-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
|
@Override
|
||||||
public void onError(Exception exception) {
|
public void onRecoverableError(Exception exception) {
|
||||||
exception.printStackTrace();
|
exception.printStackTrace();
|
||||||
Toast.makeText(context, "Failed to play this audio", Toast.LENGTH_SHORT).show();
|
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
|
// 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
|
@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 the current window is seekable, then the error is produced by transitioning into
|
switch (error.type) {
|
||||||
// bad window, therefore we simply increment the current index.
|
case ExoPlaybackException.TYPE_SOURCE:
|
||||||
// This is done because ExoPlayer reports the exception before window is
|
if (simpleExoPlayer.getDuration() < 0 || simpleExoPlayer.getCurrentPosition() < 0) {
|
||||||
// transitioned due to seamless playback.
|
playQueue.error();
|
||||||
if (!simpleExoPlayer.isCurrentWindowSeekable()) {
|
onRecoverableError(error);
|
||||||
playQueue.error();
|
} else {
|
||||||
onError(error);
|
playQueue.offsetIndex(+1);
|
||||||
} else {
|
}
|
||||||
playQueue.offsetIndex(+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Player error causes ExoPlayer to go back to IDLE state, which requires resetting
|
playbackManager.reset();
|
||||||
// preparing a new media source.
|
playbackManager.load();
|
||||||
playbackManager.reset();
|
break;
|
||||||
playbackManager.load();
|
default:
|
||||||
|
onUnrecoverableError(error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -752,7 +775,9 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||||
// General Player
|
// 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) {
|
public void onPrepared(boolean playWhenReady) {
|
||||||
if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
|
if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
|
||||||
|
|
|
@ -352,11 +352,18 @@ public final class MainVideoPlayer extends Activity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(Exception exception) {
|
public void onRecoverableError(Exception exception) {
|
||||||
exception.printStackTrace();
|
exception.printStackTrace();
|
||||||
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
|
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
|
// States
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
|
@ -463,11 +463,18 @@ public final class PopupVideoPlayer extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(Exception exception) {
|
public void onRecoverableError(Exception exception) {
|
||||||
exception.printStackTrace();
|
exception.printStackTrace();
|
||||||
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
|
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
|
@Override
|
||||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
super.onStopTrackingTouch(seekBar);
|
super.onStopTrackingTouch(seekBar);
|
||||||
|
|
|
@ -16,6 +16,7 @@ import java.io.IOException;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.functions.Consumer;
|
import io.reactivex.functions.Consumer;
|
||||||
|
import io.reactivex.functions.Function;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -113,44 +114,60 @@ public final class DeferredMediaSource implements MediaSource {
|
||||||
|
|
||||||
Log.d(TAG, "Loading: [" + stream.getTitle() + "] with url: " + stream.getUrl());
|
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
|
@Override
|
||||||
public void accept(StreamInfo streamInfo) throws Exception {
|
public MediaSource apply(StreamInfo streamInfo) throws Exception {
|
||||||
onStreamInfoReceived(streamInfo);
|
return onStreamInfoReceived(streamInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final Consumer<MediaSource> onSuccess = new Consumer<MediaSource>() {
|
||||||
|
@Override
|
||||||
|
public void accept(MediaSource mediaSource) throws Exception {
|
||||||
|
onMediaSourceReceived(mediaSource);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final Consumer<Throwable> onError = new Consumer<Throwable>() {
|
final Consumer<Throwable> onError = new Consumer<Throwable>() {
|
||||||
@Override
|
@Override
|
||||||
public void accept(Throwable throwable) throws Exception {
|
public void accept(Throwable throwable) throws Exception {
|
||||||
onStreamInfoError(throwable);
|
onStreamInfoError(throwable);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loader = stream.getStream()
|
loader = stream.getStream()
|
||||||
.subscribeOn(Schedulers.io())
|
.observeOn(Schedulers.io())
|
||||||
|
.map(onReceive)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(onSuccess, onError);
|
.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());
|
Log.d(TAG, " Loaded: [" + stream.getTitle() + "] with url: " + stream.getUrl());
|
||||||
state = STATE_LOADED;
|
state = STATE_LOADED;
|
||||||
|
|
||||||
if (exoPlayer == null || listener == null || streamInfo == null) {
|
this.mediaSource = mediaSource;
|
||||||
error = new Throwable("Stream info loading failed. URL: " + stream.getUrl());
|
this.mediaSource.prepareSource(exoPlayer, false, listener);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onStreamInfoError(final Throwable throwable) {
|
private void onStreamInfoError(final Throwable throwable) {
|
||||||
|
|
Loading…
Reference in a new issue