Separate buffering from playbackState. Improve Android buffering detection.

This commit is contained in:
Ryan Heise 2020-02-05 12:26:21 +11:00
parent 280f1a8208
commit eee50d712e
5 changed files with 84 additions and 104 deletions

View file

@ -35,23 +35,9 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
private final MethodChannel methodChannel;
private final EventChannel eventChannel;
private EventSink eventSink;
private final Handler handler = new Handler();
private final Runnable positionObserver = new Runnable() {
@Override
public void run() {
if (state != PlaybackState.playing && state != PlaybackState.buffering)
return;
if (eventSink != null) {
checkForDiscontinuity();
}
handler.postDelayed(this, 200);
}
};
private final String id;
private volatile PlaybackState state;
private PlaybackState stateBeforeSeek;
private long updateTime;
private long updatePosition;
@ -64,6 +50,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
private Result prepareResult;
private Result seekResult;
private boolean seekProcessed;
private boolean buffering;
private MediaSource mediaSource;
private SimpleExoPlayer player;
@ -106,15 +93,19 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
completeSeek();
}
break;
case Player.STATE_BUFFERING:
// TODO: use this instead of checkForDiscontinuity.
break;
case Player.STATE_ENDED:
if (state != PlaybackState.completed) {
transition(PlaybackState.completed);
}
break;
}
final boolean buffering = playbackState == Player.STATE_BUFFERING;
// don't notify buffering if (buffering && state == stopped)
final boolean notifyBuffering = !buffering || state != PlaybackState.stopped;
if (notifyBuffering && (buffering != this.buffering)) {
this.buffering = buffering;
broadcastPlaybackEvent();
}
}
@Override
@ -130,29 +121,10 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
private void completeSeek() {
seekProcessed = false;
seekPos = null;
transition(stateBeforeSeek);
seekResult.success(null);
stateBeforeSeek = null;
seekResult = null;
}
private void checkForDiscontinuity() {
final long now = System.currentTimeMillis();
final long position = getCurrentPosition();
final long timeSinceLastUpdate = now - updateTime;
final long expectedPosition = updatePosition + (long)(timeSinceLastUpdate * speed);
final long drift = position - expectedPosition;
// Update if we've drifted or just started observing
if (updateTime == 0L) {
broadcastPlaybackEvent();
} else if (drift < -100) {
System.out.println("time discontinuity detected: " + drift);
transition(PlaybackState.buffering);
} else if (state == PlaybackState.buffering) {
transition(PlaybackState.playing);
}
}
@Override
public void onMethodCall(final MethodCall call, final Result result) {
final List<?> args = (List<?>)call.arguments;
@ -219,6 +191,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
private void broadcastPlaybackEvent() {
final ArrayList<Object> event = new ArrayList<Object>();
event.add(state.ordinal());
event.add(buffering);
event.add(updatePosition = getCurrentPosition());
event.add(updateTime = System.currentTimeMillis());
eventSink.success(event);
@ -237,9 +210,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
private void transition(final PlaybackState newState) {
final PlaybackState oldState = state;
state = newState;
if (oldState != PlaybackState.playing && newState == PlaybackState.playing) {
startObservingPosition();
}
broadcastPlaybackEvent();
}
@ -282,14 +252,9 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
break;
case stopped:
case completed:
case buffering:
case paused:
transition(PlaybackState.playing);
player.setPlayWhenReady(true);
if (seekResult != null) {
stateBeforeSeek = PlaybackState.playing;
} else {
transition(PlaybackState.playing);
}
break;
default:
throw new IllegalStateException("Cannot call play from connecting/none states (" + state + ")");
@ -301,12 +266,8 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
case paused:
break;
case playing:
case buffering:
player.setPlayWhenReady(false);
transition(PlaybackState.paused);
if (seekResult != null) {
stateBeforeSeek = PlaybackState.paused;
}
break;
default:
throw new IllegalStateException("Can call pause only from playing and buffering states (" + state + ")");
@ -320,18 +281,17 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
break;
case connecting:
abortExistingConnection();
buffering = false;
transition(PlaybackState.stopped);
result.success(null);
break;
case buffering:
abortSeek();
// no break
case completed:
case playing:
case paused:
abortSeek();
player.setPlayWhenReady(false);
player.seekTo(0L);
transition(PlaybackState.stopped);
player.seekTo(0L);
result.success(null);
break;
default:
@ -358,11 +318,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
seekPos = position;
seekResult = result;
seekProcessed = false;
if (stateBeforeSeek == null) {
stateBeforeSeek = state;
}
handler.removeCallbacks(positionObserver);
transition(PlaybackState.buffering);
player.seekTo(position);
}
@ -376,7 +331,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
seekResult.success(null);
seekResult = null;
seekPos = null;
stateBeforeSeek = null;
seekProcessed = false;
}
}
@ -388,17 +342,11 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
}
}
private void startObservingPosition() {
handler.removeCallbacks(positionObserver);
handler.post(positionObserver);
}
enum PlaybackState {
none,
stopped,
paused,
playing,
buffering,
connecting,
completed
}