From af679100eab9126b6092b5401b8f413c541db57c Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Wed, 1 Jan 2020 21:40:25 +1100 Subject: [PATCH] Make states more permissive --- .../com/ryanheise/just_audio/AudioPlayer.java | 36 ++++++++++---- lib/just_audio.dart | 49 ++++++------------- 2 files changed, 41 insertions(+), 44 deletions(-) diff --git a/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java b/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java index e55d3ba..cf1c961 100644 --- a/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java +++ b/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java @@ -223,10 +223,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener { } private void transition(final PlaybackState newState) { - transition(state, newState); - } - - private void transition(final PlaybackState oldState, final PlaybackState newState) { + final PlaybackState oldState = state; state = newState; if (oldState != PlaybackState.playing && newState == PlaybackState.playing) { startObservingPosition(); @@ -235,6 +232,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener { } public void setUrl(final String url, final Result result) throws IOException { + abortExistingConnection(); prepareResult = result; transition(PlaybackState.connecting); 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) { + if (state == PlaybackState.none) { + throw new IllegalStateException("Cannot call setClip from none state"); + } + abortExistingConnection(); this.start = start; this.end = end; prepareResult = result; @@ -264,6 +266,8 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener { public void play() { switch (state) { + case playing: + break; case stopped: case completed: case buffering: @@ -272,12 +276,14 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener { transition(PlaybackState.playing); break; 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() { switch (state) { + case paused: + break; case playing: case buffering: player.setPlayWhenReady(false); @@ -293,7 +299,10 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener { case stopped: result.success(null); break; - // TODO: Allow stopping from connecting states. + case connecting: + transition(PlaybackState.stopped); + result.success(null); + break; case completed: case playing: case buffering: @@ -304,7 +313,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener { result.success(null); break; 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) { + if (state == PlaybackState.none || state == PlaybackState.connecting) { + throw new IllegalStateException("Cannot call seek from none none/connecting states"); + } seekPos = position; seekResult = result; if (stateBeforeSeek == null) { @@ -331,13 +343,17 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener { } 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(); transition(PlaybackState.none); } + private void abortExistingConnection() { + if (prepareResult != null) { + prepareResult.error("setUrl aborted", null, null); + prepareResult = null; + } + } + private void startObservingPosition() { handler.removeCallbacks(positionObserver); handler.post(positionObserver); diff --git a/lib/just_audio.dart b/lib/just_audio.dart index e941c41..9894fda 100644 --- a/lib/just_audio.dart +++ b/lib/just_audio.dart @@ -30,11 +30,10 @@ import 'package:rxdart/rxdart.dart'; /// /// The [AudioPlayer] instance transitions through different states as follows: /// -/// * [AudioPlaybackState.none]: immediately after instantiation. -/// * [AudioPlaybackState.stopped]: eventually after [setUrl], [setFilePath] or -/// [setAsset] completes, and immediately after [stop]. -/// * [AudioPlaybackState.paused]: after [pause] and after reaching the end of -/// the requested [play] segment. +/// * [AudioPlaybackState.none]: immediately after instantiation and [dispose]. +/// * [AudioPlaybackState.stopped]: eventually after [setUrl], [setFilePath], +/// [setAsset] or [setClip] completes, and immediately after [stop]. +/// * [AudioPlaybackState.paused]: after [pause]. /// * [AudioPlaybackState.playing]: after [play] and after sufficiently /// buffering during normal playback. /// * [AudioPlaybackState.buffering]: immediately after a seek request and @@ -42,7 +41,7 @@ import 'package:rxdart/rxdart.dart'; /// * [AudioPlaybackState.connecting]: immediately after [setUrl], /// [setFilePath] and [setAsset] while waiting for the media to load. /// * [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 /// whatever state the player was in prior to the seek request. @@ -131,12 +130,7 @@ class AudioPlayer { /// The current speed of the player. double get speed => _speed; - /// 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] + /// Loads audio media from a URL and returns the duration of that audio. Future setUrl(final String url) async { _durationFuture = _invokeMethod('setUrl', [url]).then((ms) => Duration(milliseconds: ms)); @@ -145,21 +139,11 @@ class AudioPlayer { return duration; } - /// 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] + /// Loads audio media from a file and returns the duration of that audio. Future setFilePath(final String filePath) => setUrl('file://$filePath'); /// 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 setAsset(final String assetPath) async { final file = await _cacheFile; if (!file.existsSync()) { @@ -173,7 +157,8 @@ class AudioPlayer { Future get _cacheFile async => File(p.join( (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 setClip({Duration start, Duration end}) async { _durationFuture = _invokeMethod('setClip', [start?.inMilliseconds, end?.inMilliseconds]) @@ -185,12 +170,10 @@ class AudioPlayer { /// Plays the currently loaded media from the current position. The [Future] /// 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 - /// states: + /// stopped. This method can be called from any state except for: /// - /// * [AudioPlaybackState.stopped] - /// * [AudioPlaybackState.completed] - /// * [AudioPlaybackState.paused] + /// * [AudioPlaybackState.connecting] + /// * [AudioPlaybackState.none] Future play() async { StreamSubscription subscription; Completer completer = Completer(); @@ -228,7 +211,6 @@ class AudioPlayer { /// /// * [AudioPlaybackState.playing] /// * [AudioPlaybackState.paused] - /// * [AudioPlaybackState.stopped] /// * [AudioPlaybackState.completed] Future stop() async { await _invokeMethod('stop'); @@ -254,12 +236,11 @@ class AudioPlayer { } /// 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 - /// only from the following states: + /// after you are done with the player. This method can be invoked from any + /// state except for: /// - /// * [AudioPlaybackState.stopped] - /// * [AudioPlaybackState.completed] /// * [AudioPlaybackState.none] + /// * [AudioPlaybackState.connecting] Future dispose() async { if ((await _cacheFile).existsSync()) { (await _cacheFile).deleteSync();