Add completed state

This commit is contained in:
Ryan Heise 2019-12-27 16:43:09 +11:00
parent ff20386e96
commit dfd1a193ed
4 changed files with 59 additions and 18 deletions

View File

@ -199,8 +199,8 @@ public class AudioPlayer implements MethodCallHandler {
} }
public void setUrl(final String url, final Result result) throws IOException { public void setUrl(final String url, final Result result) throws IOException {
if (state != PlaybackState.none && state != PlaybackState.stopped) { if (state != PlaybackState.none && state != PlaybackState.stopped && state != PlaybackState.completed) {
throw new IllegalStateException("Can call setUrl only from none and stopped states"); throw new IllegalStateException("Can call setUrl only from none/stopped/completed states");
} }
transition(PlaybackState.connecting); transition(PlaybackState.connecting);
this.url = url; this.url = url;
@ -231,6 +231,7 @@ public class AudioPlayer implements MethodCallHandler {
this.untilPosition = untilPosition; this.untilPosition = untilPosition;
switch (state) { switch (state) {
case stopped: case stopped:
case completed:
ensureStopped(); ensureStopped();
transition(PlaybackState.playing); transition(PlaybackState.playing);
playThread = new PlayThread(); playThread = new PlayThread();
@ -243,7 +244,7 @@ public class AudioPlayer implements MethodCallHandler {
} }
break; break;
default: default:
throw new IllegalStateException("Can call play only from stopped and paused states (" + state + ")"); throw new IllegalStateException("Can call play only from stopped, completed and paused states (" + state + ")");
} }
} }
@ -276,6 +277,9 @@ public class AudioPlayer implements MethodCallHandler {
switch (state) { switch (state) {
case stopped: case stopped:
break; break;
case completed:
transition(PlaybackState.stopped);
break;
// TODO: Allow stopping from buffered state. // TODO: Allow stopping from buffered state.
case playing: case playing:
case paused: case paused:
@ -350,8 +354,8 @@ public class AudioPlayer implements MethodCallHandler {
} }
public void dispose() { public void dispose() {
if (state != PlaybackState.stopped && state != PlaybackState.none) { if (state != PlaybackState.stopped && state != PlaybackState.completed && state != PlaybackState.none) {
throw new IllegalStateException("Can call dispose only from stopped and none states"); throw new IllegalStateException("Can call dispose only from stopped/completed/none states");
} }
if (extractor != null) { if (extractor != null) {
ensureStopped(); ensureStopped();
@ -416,7 +420,7 @@ public class AudioPlayer implements MethodCallHandler {
@Override @Override
public void run() { public void run() {
boolean reachedEnd = false;
int encoding = AudioFormat.ENCODING_PCM_16BIT; int encoding = AudioFormat.ENCODING_PCM_16BIT;
int channelFormat = channelCount==1?AudioFormat.CHANNEL_OUT_MONO:AudioFormat.CHANNEL_OUT_STEREO; int channelFormat = channelCount==1?AudioFormat.CHANNEL_OUT_MONO:AudioFormat.CHANNEL_OUT_STEREO;
int minSize = AudioTrack.getMinBufferSize(sampleRate, channelFormat, encoding); int minSize = AudioTrack.getMinBufferSize(sampleRate, channelFormat, encoding);
@ -532,6 +536,7 @@ public class AudioPlayer implements MethodCallHandler {
} else { } else {
audioTrack.pause(); audioTrack.pause();
finishedDecoding = true; finishedDecoding = true;
reachedEnd = true;
} }
} else if (untilPosition != null && currentPosition >= untilPosition) { } else if (untilPosition != null && currentPosition >= untilPosition) {
// NOTE: When streaming audio over bluetooth, it clips off // NOTE: When streaming audio over bluetooth, it clips off
@ -570,8 +575,9 @@ public class AudioPlayer implements MethodCallHandler {
synchronized (monitor) { synchronized (monitor) {
start = 0; start = 0;
untilPosition = null; untilPosition = null;
bgTransition(PlaybackState.stopped); bgTransition(reachedEnd ? PlaybackState.completed : PlaybackState.stopped);
extractor.seekTo(0L, MediaExtractor.SEEK_TO_CLOSEST_SYNC); extractor.seekTo(0L, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
handler.post(() -> broadcastPlaybackEvent());
playThread = null; playThread = null;
monitor.notifyAll(); monitor.notifyAll();
} }
@ -624,7 +630,8 @@ public class AudioPlayer implements MethodCallHandler {
paused, paused,
playing, playing,
buffering, buffering,
connecting connecting,
completed
} }
class SeekRequest { class SeekRequest {

View File

@ -12,5 +12,6 @@ enum PlaybackState {
paused, paused,
playing, playing,
buffering, buffering,
connecting connecting,
completed
}; };

View File

@ -164,7 +164,7 @@
queue:nil queue:nil
usingBlock:^(NSNotification* note) { usingBlock:^(NSNotification* note) {
NSLog(@"Reached play end time"); NSLog(@"Reached play end time");
[self stop]; [self complete];
} }
]; ];
if (_player) { if (_player) {
@ -258,6 +258,14 @@
}]; }];
} }
- (void)complete {
[_player pause];
[_player seekToTime:CMTimeMake(0, 1000)
completionHandler:^(BOOL finished) {
[self setPlaybackState:completed];
}];
}
- (void)setVolume:(float)volume { - (void)setVolume:(float)volume {
[_player setVolume:volume]; [_player setVolume:volume];
} }

View File

@ -30,8 +30,7 @@ import 'package:rxdart/rxdart.dart';
/// ///
/// * [AudioPlaybackState.none]: immediately after instantiation. /// * [AudioPlaybackState.none]: immediately after instantiation.
/// * [AudioPlaybackState.stopped]: eventually after [setUrl], [setFilePath] or /// * [AudioPlaybackState.stopped]: eventually after [setUrl], [setFilePath] or
/// [setAsset] completes, immediately after [stop], and immediately after /// [setAsset] completes, and immediately after [stop].
/// playback naturally reaches the end of the media.
/// * [AudioPlaybackState.paused]: after [pause] and after reaching the end of /// * [AudioPlaybackState.paused]: after [pause] and after reaching the end of
/// the requested [play] segment. /// the requested [play] segment.
/// * [AudioPlaybackState.playing]: after [play] and after sufficiently /// * [AudioPlaybackState.playing]: after [play] and after sufficiently
@ -40,6 +39,8 @@ import 'package:rxdart/rxdart.dart';
/// during normal playback when the next buffer is not ready to be played. /// 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
/// end of the media.
/// ///
/// Additionally, after a [seek] request completes, the state will return to /// Additionally, after a [seek] request completes, the state will return to
/// whatever state the player was in prior to the seek request. /// whatever state the player was in prior to the seek request.
@ -128,7 +129,12 @@ class AudioPlayer {
/// The current speed of the player. /// The current speed of the player.
double get speed => _speed; double get speed => _speed;
/// Loads audio media from a URL and returns the duration of that audio. /// Loads audio media from a URL and returns the duration of that audio. It
/// is legal to invoke this method only from one of the following states:
///
/// * [AudioPlaybackState.none]
/// * [AudioPlaybackState.stopped]
/// * [AudioPlaybackState.completed]
Future<Duration> setUrl(final String url) async { Future<Duration> setUrl(final String url) async {
_durationFuture = _durationFuture =
_invokeMethod('setUrl', [url]).then((ms) => Duration(milliseconds: ms)); _invokeMethod('setUrl', [url]).then((ms) => Duration(milliseconds: ms));
@ -137,11 +143,21 @@ class AudioPlayer {
return duration; return duration;
} }
/// Loads audio media from a file and returns the duration of that audio. /// Loads audio media from a file and returns the duration of that audio. It
/// is legal to invoke this method only from one of the following states:
///
/// * [AudioPlaybackState.none]
/// * [AudioPlaybackState.stopped]
/// * [AudioPlaybackState.completed]
Future<Duration> setFilePath(final String filePath) => Future<Duration> setFilePath(final String filePath) =>
setUrl('file://$filePath'); setUrl('file://$filePath');
/// Loads audio media from an asset and returns the duration of that audio. /// Loads audio media from an asset and returns the duration of that audio.
/// It is legal to invoke this method only from one of the following states:
///
/// * [AudioPlaybackState.none]
/// * [AudioPlaybackState.stopped]
/// * [AudioPlaybackState.completed]
Future<Duration> setAsset(final String assetPath) async { Future<Duration> setAsset(final String assetPath) async {
final file = await _cacheFile; final file = await _cacheFile;
if (!file.existsSync()) { if (!file.existsSync()) {
@ -161,6 +177,7 @@ class AudioPlayer {
/// invoke this method only from one of the following states: /// invoke this method only from one of the following states:
/// ///
/// * [AudioPlaybackState.stopped] /// * [AudioPlaybackState.stopped]
/// * [AudioPlaybackState.completed]
/// * [AudioPlaybackState.paused] /// * [AudioPlaybackState.paused]
Future<void> play({final Duration untilPosition}) async { Future<void> play({final Duration untilPosition}) async {
StreamSubscription subscription; StreamSubscription subscription;
@ -194,6 +211,7 @@ class AudioPlayer {
/// * [AudioPlaybackState.playing] /// * [AudioPlaybackState.playing]
/// * [AudioPlaybackState.paused] /// * [AudioPlaybackState.paused]
/// * [AudioPlaybackState.stopped] /// * [AudioPlaybackState.stopped]
/// * [AudioPlaybackState.completed]
Future<void> stop() async { Future<void> stop() async {
await _invokeMethod('stop'); await _invokeMethod('stop');
} }
@ -210,14 +228,20 @@ class AudioPlayer {
await _invokeMethod('setSpeed', [speed]); await _invokeMethod('setSpeed', [speed]);
} }
/// Seeks to a particular position. It is legal to invoke this method /// Seeks to a particular position. It is legal to invoke this method from
/// from any state except for [AudioPlaybackState.none]. /// any state except for [AudioPlaybackState.none] and
/// [AudioPlaybackState.connecting].
Future<void> seek(final Duration position) async { Future<void> seek(final Duration position) async {
await _invokeMethod('seek', [position.inMilliseconds]); await _invokeMethod('seek', [position.inMilliseconds]);
} }
/// Release all resources associated with this player. You must invoke /// Release all resources associated with this player. You must invoke this
/// this after you are done with the player. /// after you are done with the player. It is legal to invoke this method
/// only from the following states:
///
/// * [AudioPlaybackState.stopped]
/// * [AudioPlaybackState.completed]
/// * [AudioPlaybackState.none]
Future<void> dispose() async { Future<void> dispose() async {
if ((await _cacheFile).existsSync()) { if ((await _cacheFile).existsSync()) {
(await _cacheFile).deleteSync(); (await _cacheFile).deleteSync();
@ -275,4 +299,5 @@ enum AudioPlaybackState {
playing, playing,
buffering, buffering,
connecting, connecting,
completed,
} }