Separate buffering from playbackState. Improve Android buffering detection.
This commit is contained in:
parent
280f1a8208
commit
eee50d712e
|
@ -35,23 +35,9 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
|
||||||
private final MethodChannel methodChannel;
|
private final MethodChannel methodChannel;
|
||||||
private final EventChannel eventChannel;
|
private final EventChannel eventChannel;
|
||||||
private EventSink eventSink;
|
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 final String id;
|
||||||
private volatile PlaybackState state;
|
private volatile PlaybackState state;
|
||||||
private PlaybackState stateBeforeSeek;
|
|
||||||
private long updateTime;
|
private long updateTime;
|
||||||
private long updatePosition;
|
private long updatePosition;
|
||||||
|
|
||||||
|
@ -64,6 +50,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
|
||||||
private Result prepareResult;
|
private Result prepareResult;
|
||||||
private Result seekResult;
|
private Result seekResult;
|
||||||
private boolean seekProcessed;
|
private boolean seekProcessed;
|
||||||
|
private boolean buffering;
|
||||||
private MediaSource mediaSource;
|
private MediaSource mediaSource;
|
||||||
|
|
||||||
private SimpleExoPlayer player;
|
private SimpleExoPlayer player;
|
||||||
|
@ -106,15 +93,19 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
|
||||||
completeSeek();
|
completeSeek();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Player.STATE_BUFFERING:
|
|
||||||
// TODO: use this instead of checkForDiscontinuity.
|
|
||||||
break;
|
|
||||||
case Player.STATE_ENDED:
|
case Player.STATE_ENDED:
|
||||||
if (state != PlaybackState.completed) {
|
if (state != PlaybackState.completed) {
|
||||||
transition(PlaybackState.completed);
|
transition(PlaybackState.completed);
|
||||||
}
|
}
|
||||||
break;
|
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
|
@Override
|
||||||
|
@ -130,29 +121,10 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
|
||||||
private void completeSeek() {
|
private void completeSeek() {
|
||||||
seekProcessed = false;
|
seekProcessed = false;
|
||||||
seekPos = null;
|
seekPos = null;
|
||||||
transition(stateBeforeSeek);
|
|
||||||
seekResult.success(null);
|
seekResult.success(null);
|
||||||
stateBeforeSeek = null;
|
|
||||||
seekResult = 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
|
@Override
|
||||||
public void onMethodCall(final MethodCall call, final Result result) {
|
public void onMethodCall(final MethodCall call, final Result result) {
|
||||||
final List<?> args = (List<?>)call.arguments;
|
final List<?> args = (List<?>)call.arguments;
|
||||||
|
@ -219,6 +191,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
|
||||||
private void broadcastPlaybackEvent() {
|
private void broadcastPlaybackEvent() {
|
||||||
final ArrayList<Object> event = new ArrayList<Object>();
|
final ArrayList<Object> event = new ArrayList<Object>();
|
||||||
event.add(state.ordinal());
|
event.add(state.ordinal());
|
||||||
|
event.add(buffering);
|
||||||
event.add(updatePosition = getCurrentPosition());
|
event.add(updatePosition = getCurrentPosition());
|
||||||
event.add(updateTime = System.currentTimeMillis());
|
event.add(updateTime = System.currentTimeMillis());
|
||||||
eventSink.success(event);
|
eventSink.success(event);
|
||||||
|
@ -237,9 +210,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
|
||||||
private void transition(final PlaybackState newState) {
|
private void transition(final PlaybackState newState) {
|
||||||
final PlaybackState oldState = state;
|
final PlaybackState oldState = state;
|
||||||
state = newState;
|
state = newState;
|
||||||
if (oldState != PlaybackState.playing && newState == PlaybackState.playing) {
|
|
||||||
startObservingPosition();
|
|
||||||
}
|
|
||||||
broadcastPlaybackEvent();
|
broadcastPlaybackEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,14 +252,9 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
|
||||||
break;
|
break;
|
||||||
case stopped:
|
case stopped:
|
||||||
case completed:
|
case completed:
|
||||||
case buffering:
|
|
||||||
case paused:
|
case paused:
|
||||||
|
transition(PlaybackState.playing);
|
||||||
player.setPlayWhenReady(true);
|
player.setPlayWhenReady(true);
|
||||||
if (seekResult != null) {
|
|
||||||
stateBeforeSeek = PlaybackState.playing;
|
|
||||||
} else {
|
|
||||||
transition(PlaybackState.playing);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Cannot call play from connecting/none states (" + state + ")");
|
throw new IllegalStateException("Cannot call play from connecting/none states (" + state + ")");
|
||||||
|
@ -301,12 +266,8 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
|
||||||
case paused:
|
case paused:
|
||||||
break;
|
break;
|
||||||
case playing:
|
case playing:
|
||||||
case buffering:
|
|
||||||
player.setPlayWhenReady(false);
|
player.setPlayWhenReady(false);
|
||||||
transition(PlaybackState.paused);
|
transition(PlaybackState.paused);
|
||||||
if (seekResult != null) {
|
|
||||||
stateBeforeSeek = PlaybackState.paused;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Can call pause only from playing and buffering states (" + state + ")");
|
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;
|
break;
|
||||||
case connecting:
|
case connecting:
|
||||||
abortExistingConnection();
|
abortExistingConnection();
|
||||||
|
buffering = false;
|
||||||
transition(PlaybackState.stopped);
|
transition(PlaybackState.stopped);
|
||||||
result.success(null);
|
result.success(null);
|
||||||
break;
|
break;
|
||||||
case buffering:
|
|
||||||
abortSeek();
|
|
||||||
// no break
|
|
||||||
case completed:
|
case completed:
|
||||||
case playing:
|
case playing:
|
||||||
case paused:
|
case paused:
|
||||||
|
abortSeek();
|
||||||
player.setPlayWhenReady(false);
|
player.setPlayWhenReady(false);
|
||||||
player.seekTo(0L);
|
|
||||||
transition(PlaybackState.stopped);
|
transition(PlaybackState.stopped);
|
||||||
|
player.seekTo(0L);
|
||||||
result.success(null);
|
result.success(null);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -358,11 +318,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
|
||||||
seekPos = position;
|
seekPos = position;
|
||||||
seekResult = result;
|
seekResult = result;
|
||||||
seekProcessed = false;
|
seekProcessed = false;
|
||||||
if (stateBeforeSeek == null) {
|
|
||||||
stateBeforeSeek = state;
|
|
||||||
}
|
|
||||||
handler.removeCallbacks(positionObserver);
|
|
||||||
transition(PlaybackState.buffering);
|
|
||||||
player.seekTo(position);
|
player.seekTo(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,7 +331,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
|
||||||
seekResult.success(null);
|
seekResult.success(null);
|
||||||
seekResult = null;
|
seekResult = null;
|
||||||
seekPos = null;
|
seekPos = null;
|
||||||
stateBeforeSeek = null;
|
|
||||||
seekProcessed = false;
|
seekProcessed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -388,17 +342,11 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startObservingPosition() {
|
|
||||||
handler.removeCallbacks(positionObserver);
|
|
||||||
handler.post(positionObserver);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum PlaybackState {
|
enum PlaybackState {
|
||||||
none,
|
none,
|
||||||
stopped,
|
stopped,
|
||||||
paused,
|
paused,
|
||||||
playing,
|
playing,
|
||||||
buffering,
|
|
||||||
connecting,
|
connecting,
|
||||||
completed
|
completed
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,27 +43,29 @@ class _MyAppState extends State<MyApp> {
|
||||||
children: [
|
children: [
|
||||||
Text("Science Friday"),
|
Text("Science Friday"),
|
||||||
Text("Science Friday and WNYC Studios"),
|
Text("Science Friday and WNYC Studios"),
|
||||||
StreamBuilder<AudioPlaybackState>(
|
StreamBuilder<FullAudioPlaybackState>(
|
||||||
stream: _player.playbackStateStream,
|
stream: _player.fullPlaybackStateStream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final state = snapshot.data;
|
final fullState = snapshot.data;
|
||||||
|
final state = fullState?.state;
|
||||||
|
final buffering = fullState?.buffering;
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (state == AudioPlaybackState.playing)
|
if (state == AudioPlaybackState.connecting ||
|
||||||
IconButton(
|
buffering == true)
|
||||||
icon: Icon(Icons.pause),
|
|
||||||
iconSize: 64.0,
|
|
||||||
onPressed: _player.pause,
|
|
||||||
)
|
|
||||||
else if (state == AudioPlaybackState.buffering ||
|
|
||||||
state == AudioPlaybackState.connecting)
|
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.all(8.0),
|
margin: EdgeInsets.all(8.0),
|
||||||
width: 64.0,
|
width: 64.0,
|
||||||
height: 64.0,
|
height: 64.0,
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
)
|
)
|
||||||
|
else if (state == AudioPlaybackState.playing)
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.pause),
|
||||||
|
iconSize: 64.0,
|
||||||
|
onPressed: _player.pause,
|
||||||
|
)
|
||||||
else
|
else
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.play_arrow),
|
icon: Icon(Icons.play_arrow),
|
||||||
|
|
|
@ -11,7 +11,6 @@ enum PlaybackState {
|
||||||
stopped,
|
stopped,
|
||||||
paused,
|
paused,
|
||||||
playing,
|
playing,
|
||||||
buffering,
|
|
||||||
connecting,
|
connecting,
|
||||||
completed
|
completed
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,11 +10,11 @@
|
||||||
NSString* _playerId;
|
NSString* _playerId;
|
||||||
AVPlayer* _player;
|
AVPlayer* _player;
|
||||||
enum PlaybackState _state;
|
enum PlaybackState _state;
|
||||||
enum PlaybackState _stateBeforeSeek;
|
|
||||||
long long _updateTime;
|
long long _updateTime;
|
||||||
int _updatePosition;
|
int _updatePosition;
|
||||||
int _seekPos;
|
int _seekPos;
|
||||||
FlutterResult _connectionResult;
|
FlutterResult _connectionResult;
|
||||||
|
BOOL _buffering;
|
||||||
id _endObserver;
|
id _endObserver;
|
||||||
id _timeObserver;
|
id _timeObserver;
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,9 @@
|
||||||
binaryMessenger:[registrar messenger]];
|
binaryMessenger:[registrar messenger]];
|
||||||
[_eventChannel setStreamHandler:self];
|
[_eventChannel setStreamHandler:self];
|
||||||
_state = none;
|
_state = none;
|
||||||
_stateBeforeSeek = none;
|
|
||||||
_player = nil;
|
_player = nil;
|
||||||
_seekPos = -1;
|
_seekPos = -1;
|
||||||
|
_buffering = NO;
|
||||||
_endObserver = 0;
|
_endObserver = 0;
|
||||||
_timeObserver = 0;
|
_timeObserver = 0;
|
||||||
__weak __typeof__(self) weakSelf = self;
|
__weak __typeof__(self) weakSelf = self;
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
|
|
||||||
- (void)checkForDiscontinuity {
|
- (void)checkForDiscontinuity {
|
||||||
if (!_eventSink) return;
|
if (!_eventSink) return;
|
||||||
if (_state != playing && _state != buffering) return;
|
if ((_state != playing) && !_buffering) return;
|
||||||
long long now = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
|
long long now = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
|
||||||
int position = [self getCurrentPosition];
|
int position = [self getCurrentPosition];
|
||||||
long long timeSinceLastUpdate = now - _updateTime;
|
long long timeSinceLastUpdate = now - _updateTime;
|
||||||
|
@ -105,9 +105,11 @@
|
||||||
[self broadcastPlaybackEvent];
|
[self broadcastPlaybackEvent];
|
||||||
} else if (drift < -100) {
|
} else if (drift < -100) {
|
||||||
NSLog(@"time discontinuity detected: %lld", drift);
|
NSLog(@"time discontinuity detected: %lld", drift);
|
||||||
[self setPlaybackState:buffering];
|
_buffering = YES;
|
||||||
} else if (_state == buffering) {
|
[self broadcastPlaybackEvent];
|
||||||
[self setPlaybackState:playing];
|
} else if (_buffering) {
|
||||||
|
_buffering = NO;
|
||||||
|
[self broadcastPlaybackEvent];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +120,8 @@
|
||||||
_eventSink(@[
|
_eventSink(@[
|
||||||
// state
|
// state
|
||||||
@(_state),
|
@(_state),
|
||||||
|
// buffering
|
||||||
|
@(_buffering),
|
||||||
// updatePosition
|
// updatePosition
|
||||||
@(_updatePosition),
|
@(_updatePosition),
|
||||||
// updateTime
|
// updateTime
|
||||||
|
@ -156,14 +160,14 @@
|
||||||
_endObserver = 0;
|
_endObserver = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
AVPlayerItem *playerItem;
|
AVPlayerItem *playerItem;
|
||||||
|
|
||||||
//Allow iOs playing both external links and local files.
|
//Allow iOs playing both external links and local files.
|
||||||
if ([url hasPrefix:@"file://"]) {
|
if ([url hasPrefix:@"file://"]) {
|
||||||
playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:[url substringFromIndex:7]]];
|
playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:[url substringFromIndex:7]]];
|
||||||
} else {
|
} else {
|
||||||
playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:url]];
|
playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:url]];
|
||||||
}
|
}
|
||||||
|
|
||||||
[playerItem addObserver:self
|
[playerItem addObserver:self
|
||||||
forKeyPath:@"status"
|
forKeyPath:@"status"
|
||||||
|
@ -292,10 +296,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)seek:(int)position result:(FlutterResult)result {
|
- (void)seek:(int)position result:(FlutterResult)result {
|
||||||
_stateBeforeSeek = _state;
|
|
||||||
_seekPos = position;
|
_seekPos = position;
|
||||||
NSLog(@"seek. enter buffering");
|
NSLog(@"seek. enter buffering");
|
||||||
[self setPlaybackState:buffering];
|
_buffering = YES;
|
||||||
|
[self broadcastPlaybackEvent];
|
||||||
[_player seekToTime:CMTimeMake(position, 1000)
|
[_player seekToTime:CMTimeMake(position, 1000)
|
||||||
completionHandler:^(BOOL finished) {
|
completionHandler:^(BOOL finished) {
|
||||||
NSLog(@"seek completed");
|
NSLog(@"seek completed");
|
||||||
|
@ -305,8 +309,8 @@
|
||||||
|
|
||||||
- (void)onSeekCompletion:(FlutterResult)result {
|
- (void)onSeekCompletion:(FlutterResult)result {
|
||||||
_seekPos = -1;
|
_seekPos = -1;
|
||||||
[self setPlaybackState:_stateBeforeSeek];
|
_buffering = NO;
|
||||||
_stateBeforeSeek = none;
|
[self broadcastPlaybackEvent];
|
||||||
result(nil);
|
result(nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,10 +34,7 @@ import 'package:rxdart/rxdart.dart';
|
||||||
/// * [AudioPlaybackState.stopped]: eventually after [setUrl], [setFilePath],
|
/// * [AudioPlaybackState.stopped]: eventually after [setUrl], [setFilePath],
|
||||||
/// [setAsset] or [setClip] completes, and immediately after [stop].
|
/// [setAsset] or [setClip] completes, and immediately after [stop].
|
||||||
/// * [AudioPlaybackState.paused]: after [pause].
|
/// * [AudioPlaybackState.paused]: after [pause].
|
||||||
/// * [AudioPlaybackState.playing]: after [play] and after sufficiently
|
/// * [AudioPlaybackState.playing]: after [play].
|
||||||
/// buffering during normal playback.
|
|
||||||
/// * [AudioPlaybackState.buffering]: immediately after a seek request and
|
|
||||||
/// during normal playback when the next buffer is not ready to be played.
|
|
||||||
/// * [AudioPlaybackState.connecting]: immediately after [setUrl],
|
/// * [AudioPlaybackState.connecting]: immediately after [setUrl],
|
||||||
/// [setFilePath] and [setAsset] while waiting for the media to load.
|
/// [setFilePath] and [setAsset] while waiting for the media to load.
|
||||||
/// * [AudioPlaybackState.completed]: immediately after playback reaches the
|
/// * [AudioPlaybackState.completed]: immediately after playback reaches the
|
||||||
|
@ -64,6 +61,7 @@ class AudioPlayer {
|
||||||
// TODO: also broadcast this event on instantiation.
|
// TODO: also broadcast this event on instantiation.
|
||||||
AudioPlaybackEvent _audioPlaybackEvent = AudioPlaybackEvent(
|
AudioPlaybackEvent _audioPlaybackEvent = AudioPlaybackEvent(
|
||||||
state: AudioPlaybackState.none,
|
state: AudioPlaybackState.none,
|
||||||
|
buffering: false,
|
||||||
updatePosition: Duration.zero,
|
updatePosition: Duration.zero,
|
||||||
updateTime: Duration.zero,
|
updateTime: Duration.zero,
|
||||||
speed: 1.0,
|
speed: 1.0,
|
||||||
|
@ -77,6 +75,10 @@ class AudioPlayer {
|
||||||
|
|
||||||
final _playbackStateSubject = BehaviorSubject<AudioPlaybackState>();
|
final _playbackStateSubject = BehaviorSubject<AudioPlaybackState>();
|
||||||
|
|
||||||
|
final _bufferingSubject = BehaviorSubject<bool>();
|
||||||
|
|
||||||
|
final _fullPlaybackStateSubject = BehaviorSubject<FullAudioPlaybackState>();
|
||||||
|
|
||||||
double _volume = 1.0;
|
double _volume = 1.0;
|
||||||
|
|
||||||
double _speed = 1.0;
|
double _speed = 1.0;
|
||||||
|
@ -90,14 +92,22 @@ class AudioPlayer {
|
||||||
.receiveBroadcastStream()
|
.receiveBroadcastStream()
|
||||||
.map((data) => _audioPlaybackEvent = AudioPlaybackEvent(
|
.map((data) => _audioPlaybackEvent = AudioPlaybackEvent(
|
||||||
state: AudioPlaybackState.values[data[0]],
|
state: AudioPlaybackState.values[data[0]],
|
||||||
updatePosition: Duration(milliseconds: data[1]),
|
buffering: data[1],
|
||||||
updateTime: Duration(milliseconds: data[2]),
|
updatePosition: Duration(milliseconds: data[2]),
|
||||||
|
updateTime: Duration(milliseconds: data[3]),
|
||||||
speed: _speed,
|
speed: _speed,
|
||||||
));
|
));
|
||||||
_eventChannelStreamSubscription =
|
_eventChannelStreamSubscription =
|
||||||
_eventChannelStream.listen(_playbackEventSubject.add);
|
_eventChannelStream.listen(_playbackEventSubject.add);
|
||||||
_playbackStateSubject
|
_playbackStateSubject
|
||||||
.addStream(playbackEventStream.map((state) => state.state).distinct());
|
.addStream(playbackEventStream.map((state) => state.state).distinct());
|
||||||
|
_bufferingSubject.addStream(
|
||||||
|
playbackEventStream.map((state) => state.buffering).distinct());
|
||||||
|
_fullPlaybackStateSubject.addStream(
|
||||||
|
Rx.combineLatest2<AudioPlaybackState, bool, FullAudioPlaybackState>(
|
||||||
|
playbackStateStream,
|
||||||
|
bufferingStream,
|
||||||
|
(state, buffering) => FullAudioPlaybackState(state, buffering)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The duration of any media set via [setUrl], [setFilePath] or [setAsset],
|
/// The duration of any media set via [setUrl], [setFilePath] or [setAsset],
|
||||||
|
@ -121,6 +131,16 @@ class AudioPlayer {
|
||||||
Stream<AudioPlaybackState> get playbackStateStream =>
|
Stream<AudioPlaybackState> get playbackStateStream =>
|
||||||
_playbackStateSubject.stream;
|
_playbackStateSubject.stream;
|
||||||
|
|
||||||
|
/// Whether the player is buffering.
|
||||||
|
bool get buffering => _audioPlaybackEvent.buffering;
|
||||||
|
|
||||||
|
/// A stream of buffering state changes.
|
||||||
|
Stream<bool> get bufferingStream => _bufferingSubject.stream;
|
||||||
|
|
||||||
|
/// A stream of [FullAudioPlaybackState]s.
|
||||||
|
Stream<FullAudioPlaybackState> get fullPlaybackStateStream =>
|
||||||
|
_fullPlaybackStateSubject.stream;
|
||||||
|
|
||||||
/// A stream periodically tracking the current position of this player.
|
/// A stream periodically tracking the current position of this player.
|
||||||
Stream<Duration> getPositionStream(
|
Stream<Duration> getPositionStream(
|
||||||
[final Duration period = const Duration(milliseconds: 200)]) =>
|
[final Duration period = const Duration(milliseconds: 200)]) =>
|
||||||
|
@ -209,10 +229,7 @@ class AudioPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pauses the currently playing media. It is legal to invoke this method
|
/// Pauses the currently playing media. It is legal to invoke this method
|
||||||
/// only from the following states:
|
/// only from the [AudioPlaybackState.playing] state.
|
||||||
///
|
|
||||||
/// * [AudioPlaybackState.playing]
|
|
||||||
/// * [AudioPlaybackState.buffering]
|
|
||||||
Future<void> pause() async {
|
Future<void> pause() async {
|
||||||
await _invokeMethod('pause');
|
await _invokeMethod('pause');
|
||||||
}
|
}
|
||||||
|
@ -272,6 +289,9 @@ class AudioPlaybackEvent {
|
||||||
/// The current playback state.
|
/// The current playback state.
|
||||||
final AudioPlaybackState state;
|
final AudioPlaybackState state;
|
||||||
|
|
||||||
|
/// Whether the player is buffering.
|
||||||
|
final bool buffering;
|
||||||
|
|
||||||
/// When the last time a position discontinuity happened, as measured in time
|
/// When the last time a position discontinuity happened, as measured in time
|
||||||
/// since the epoch.
|
/// since the epoch.
|
||||||
final Duration updateTime;
|
final Duration updateTime;
|
||||||
|
@ -284,6 +304,7 @@ class AudioPlaybackEvent {
|
||||||
|
|
||||||
AudioPlaybackEvent({
|
AudioPlaybackEvent({
|
||||||
@required this.state,
|
@required this.state,
|
||||||
|
@required this.buffering,
|
||||||
@required this.updateTime,
|
@required this.updateTime,
|
||||||
@required this.updatePosition,
|
@required this.updatePosition,
|
||||||
@required this.speed,
|
@required this.speed,
|
||||||
|
@ -308,7 +329,13 @@ enum AudioPlaybackState {
|
||||||
stopped,
|
stopped,
|
||||||
paused,
|
paused,
|
||||||
playing,
|
playing,
|
||||||
buffering,
|
|
||||||
connecting,
|
connecting,
|
||||||
completed,
|
completed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FullAudioPlaybackState {
|
||||||
|
final AudioPlaybackState state;
|
||||||
|
final bool buffering;
|
||||||
|
|
||||||
|
FullAudioPlaybackState(this.state, this.buffering);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue