Canardoux / flutter_sound

Flutter plugin for sound. Audio recorder and player.
Mozilla Public License 2.0
869 stars 568 forks source link

Design an event model for app life cycle change and focus change. #325

Closed bsutton closed 4 years ago

bsutton commented 4 years ago

One of the key problems with flutter sound is that it isn't a good citizen in that it doesn't play well (no pun intended) with other media players nor with app life cycle.

Particularly on android the mediaplayer is considered heavy weight as it locks down hardware acceleration for audio. If I've understood correctly then if we don't release the media player then other players won't get the benefit of hardware acceleration and possibly won't be able to play at all.

With the TrackPlayer plugin we are essentially sharing the playback 'shade' (what I've referred to as the OSs' UI) with other apps. If another app takes the focus we need to (or will be forced to) release the players UI.

So there are a number questions and design implications for this.

So I think there are four different use cases:

1) play audio only when app is active, no OS ui 2) play audio only when app is active, with OS ui 3) play audio in background, no OS ui 4) play audio in background, with OS ui.

I'm not certain I've got the terminology correct (input on this would be appreciated).

Active/InActive the application is active (in the foreground) or inactive (in the background).

Attached - We are attached (using) the OS ui. If we are attached AND focus then our audio is controllable via the OSs' ui. If we don't have focus we are still 'attached', our audio will continue to play (if it was playing before being detached) but will not be controllable via the OSs ui.

Focus - if we have focus then our audio is considered the primary audio.

Hush Others - if set and we have the focus other audio will be playing at a reduce volume. If hush others isn't set then even if we have focus other audio may be louder than ours.

Hush on call - If a call is received (or perhaps when started) we need to hush our audio. Do we completely pause the audio or just reduce the volume? Pause seems to be a better idea. Should this be an option or enforced?

To implement the above controls we need to receive events from the OS level plugins including:

onActive onInactive onFocus onDefocus onCall

Lets look at each in turn:

1) If we are just playing audio onlywithout using the OS's ui then we

Possible api changes.

SoundPlayer.withUI({bool playInBackground = false}) SoundPlayer.noUI({bool playInBackground = false});

The SoundPlayer will monitor app activate/deactive lifecycle changes.

playInBackground = false; If the app is deactivate then we will call 'stop' and release all resources and mark the seek position. If the app is re-activated we will seek to the marked position, rewind 1 second and resume playing.

playInBackground = true; We will ignore activation/deactivation events and the music will continue playing.

We could provide some convenience methods for our uses if they wanted some more specific control:

// BAD IDEA see below.
var player = SoundPlayer.withUI()
player.onDeactivated() => player.pause();
player.onActivated() => player.resume();

If we expose the onActivated/onDeactivated we need to consider resource management. i.e. do we still call stop and free resources. This feels like a bad idea as users will just forget to release the resources. I think we just take control and automate the stop/resume logic. Having said that the events may be a nice convenience but they can get them themselves and if we provide them we are likely to just increase confusion.

Attach/Detach - I don't think any action is required. I assume we can continue playing whilst detached and that we will simply loose the focus. So should we provide an option to 'pause/resume' the audio on attached detach or just provide events.

I'm thinking we just provide events as below.

var player = SoundPlayer.withUI()
player.onDetached() => player.pause();
player.onAttach() => player.resume();

To handle incoming calls we could use the same callback model:

var player = SoundPlayer.withUI()
player.onCallCommenced() => player.pause();
player.onCallCompleted() { player.rewind(Duration); player.resume(); }

Add a rewind option to make it easy for a user to reposition the playback point

player.rewind(Duration)
bsutton commented 4 years ago

@Larpoux I think I have an api that now deals with this.

The main question I have left is how do I instruct the plugin to play in the background?

github-actions[bot] commented 4 years ago

This issue is stale because it has been open 90 days with no activity. Leave a comment or this will be closed in 7 days.