Make states more permissive

This commit is contained in:
Ryan Heise 2020-01-01 21:40:25 +11:00
parent 8100b80d8d
commit af679100ea
2 changed files with 41 additions and 44 deletions

View File

@ -223,10 +223,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
} }
private void transition(final PlaybackState newState) { private void transition(final PlaybackState newState) {
transition(state, newState); final PlaybackState oldState = state;
}
private void transition(final PlaybackState oldState, final PlaybackState newState) {
state = newState; state = newState;
if (oldState != PlaybackState.playing && newState == PlaybackState.playing) { if (oldState != PlaybackState.playing && newState == PlaybackState.playing) {
startObservingPosition(); startObservingPosition();
@ -235,6 +232,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
} }
public void setUrl(final String url, final Result result) throws IOException { public void setUrl(final String url, final Result result) throws IOException {
abortExistingConnection();
prepareResult = result; prepareResult = result;
transition(PlaybackState.connecting); transition(PlaybackState.connecting);
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "just_audio")); DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "just_audio"));
@ -250,6 +248,10 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
} }
public void setClip(final Long start, final Long end, final Result result) { public void setClip(final Long start, final Long end, final Result result) {
if (state == PlaybackState.none) {
throw new IllegalStateException("Cannot call setClip from none state");
}
abortExistingConnection();
this.start = start; this.start = start;
this.end = end; this.end = end;
prepareResult = result; prepareResult = result;
@ -264,6 +266,8 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
public void play() { public void play() {
switch (state) { switch (state) {
case playing:
break;
case stopped: case stopped:
case completed: case completed:
case buffering: case buffering:
@ -272,12 +276,14 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
transition(PlaybackState.playing); transition(PlaybackState.playing);
break; break;
default: default:
throw new IllegalStateException("Can call play only from stopped, completed and paused states (" + state + ")"); throw new IllegalStateException("Cannot call play from connecting or none states (" + state + ")");
} }
} }
public void pause() { public void pause() {
switch (state) { switch (state) {
case paused:
break;
case playing: case playing:
case buffering: case buffering:
player.setPlayWhenReady(false); player.setPlayWhenReady(false);
@ -293,7 +299,10 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
case stopped: case stopped:
result.success(null); result.success(null);
break; break;
// TODO: Allow stopping from connecting states. case connecting:
transition(PlaybackState.stopped);
result.success(null);
break;
case completed: case completed:
case playing: case playing:
case buffering: case buffering:
@ -304,7 +313,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
result.success(null); result.success(null);
break; break;
default: default:
throw new IllegalStateException("Can call stop only from playing/paused/stopped/completed states (" + state + ")"); throw new IllegalStateException("Can call stop only from playing/paused/completed states (" + state + ")");
} }
} }
@ -320,6 +329,9 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
} }
public void seek(final long position, final Result result) { public void seek(final long position, final Result result) {
if (state == PlaybackState.none || state == PlaybackState.connecting) {
throw new IllegalStateException("Cannot call seek from none none/connecting states");
}
seekPos = position; seekPos = position;
seekResult = result; seekResult = result;
if (stateBeforeSeek == null) { if (stateBeforeSeek == null) {
@ -331,13 +343,17 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener {
} }
public void dispose() { public void dispose() {
if (state != PlaybackState.stopped && state != PlaybackState.completed && state != PlaybackState.none) {
throw new IllegalStateException("Can call dispose only from stopped/completed/none states (" + state + ")");
}
player.release(); player.release();
transition(PlaybackState.none); transition(PlaybackState.none);
} }
private void abortExistingConnection() {
if (prepareResult != null) {
prepareResult.error("setUrl aborted", null, null);
prepareResult = null;
}
}
private void startObservingPosition() { private void startObservingPosition() {
handler.removeCallbacks(positionObserver); handler.removeCallbacks(positionObserver);
handler.post(positionObserver); handler.post(positionObserver);

View File

@ -30,11 +30,10 @@ import 'package:rxdart/rxdart.dart';
/// ///
/// The [AudioPlayer] instance transitions through different states as follows: /// The [AudioPlayer] instance transitions through different states as follows:
/// ///
/// * [AudioPlaybackState.none]: immediately after instantiation. /// * [AudioPlaybackState.none]: immediately after instantiation and [dispose].
/// * [AudioPlaybackState.stopped]: eventually after [setUrl], [setFilePath] or /// * [AudioPlaybackState.stopped]: eventually after [setUrl], [setFilePath],
/// [setAsset] completes, and immediately after [stop]. /// [setAsset] or [setClip] completes, and immediately after [stop].
/// * [AudioPlaybackState.paused]: after [pause] and after reaching the end of /// * [AudioPlaybackState.paused]: after [pause].
/// the requested [play] segment.
/// * [AudioPlaybackState.playing]: after [play] and after sufficiently /// * [AudioPlaybackState.playing]: after [play] and after sufficiently
/// buffering during normal playback. /// buffering during normal playback.
/// * [AudioPlaybackState.buffering]: immediately after a seek request and /// * [AudioPlaybackState.buffering]: immediately after a seek request and
@ -42,7 +41,7 @@ import 'package:rxdart/rxdart.dart';
/// * [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
/// end of the media. /// end of the media or the end of the clip.
/// ///
/// 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.
@ -131,12 +130,7 @@ 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. It /// Loads audio media from a URL and returns the duration of that audio.
/// 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));
@ -145,21 +139,11 @@ class AudioPlayer {
return duration; return duration;
} }
/// Loads audio media from a file and returns the duration of that audio. It /// Loads audio media from a file and returns the duration of that audio.
/// 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()) {
@ -173,7 +157,8 @@ class AudioPlayer {
Future<File> get _cacheFile async => File(p.join( Future<File> get _cacheFile async => File(p.join(
(await getTemporaryDirectory()).path, 'just_audio_asset_cache', '$_id')); (await getTemporaryDirectory()).path, 'just_audio_asset_cache', '$_id'));
/// Clip the audio to the given [start] and [end] timestamps. /// Clip the audio to the given [start] and [end] timestamps. This method
/// cannot be called from the [AudioPlaybackState.none] state.
Future<Duration> setClip({Duration start, Duration end}) async { Future<Duration> setClip({Duration start, Duration end}) async {
_durationFuture = _durationFuture =
_invokeMethod('setClip', [start?.inMilliseconds, end?.inMilliseconds]) _invokeMethod('setClip', [start?.inMilliseconds, end?.inMilliseconds])
@ -185,12 +170,10 @@ class AudioPlayer {
/// Plays the currently loaded media from the current position. The [Future] /// Plays the currently loaded media from the current position. The [Future]
/// returned by this method completes when playback completes or is paused or /// returned by this method completes when playback completes or is paused or
/// stopped. It is legal to invoke this method only from one of the following /// stopped. This method can be called from any state except for:
/// states:
/// ///
/// * [AudioPlaybackState.stopped] /// * [AudioPlaybackState.connecting]
/// * [AudioPlaybackState.completed] /// * [AudioPlaybackState.none]
/// * [AudioPlaybackState.paused]
Future<void> play() async { Future<void> play() async {
StreamSubscription subscription; StreamSubscription subscription;
Completer completer = Completer(); Completer completer = Completer();
@ -228,7 +211,6 @@ class AudioPlayer {
/// ///
/// * [AudioPlaybackState.playing] /// * [AudioPlaybackState.playing]
/// * [AudioPlaybackState.paused] /// * [AudioPlaybackState.paused]
/// * [AudioPlaybackState.stopped]
/// * [AudioPlaybackState.completed] /// * [AudioPlaybackState.completed]
Future<void> stop() async { Future<void> stop() async {
await _invokeMethod('stop'); await _invokeMethod('stop');
@ -254,12 +236,11 @@ class AudioPlayer {
} }
/// Release all resources associated with this player. You must invoke this /// Release all resources associated with this player. You must invoke this
/// after you are done with the player. It is legal to invoke this method /// after you are done with the player. This method can be invoked from any
/// only from the following states: /// state except for:
/// ///
/// * [AudioPlaybackState.stopped]
/// * [AudioPlaybackState.completed]
/// * [AudioPlaybackState.none] /// * [AudioPlaybackState.none]
/// * [AudioPlaybackState.connecting]
Future<void> dispose() async { Future<void> dispose() async {
if ((await _cacheFile).existsSync()) { if ((await _cacheFile).existsSync()) {
(await _cacheFile).deleteSync(); (await _cacheFile).deleteSync();