ryanheise / audio_service

Flutter plugin to play audio in the background while the screen is off.
805 stars 480 forks source link

audio service background task sometimes suspends on iOS #458

Open speaking-in-code opened 4 years ago

speaking-in-code commented 4 years ago

Which API doesn't behave as documented, and how does it misbehave?

The backgroundTaskEntrypoint parameter to AudioService.start() is documented as being a normal flutter isolate. It's a bit more complicated than that on iOS. Normal flutter isolate methods (e.g. Timer from dart:async) don't work reliably in the background isolate. When iOS suspends the app, the background isolate can be paused.

The issue only seems to happen on physical iOS devices which are not charging. It does happen even when the battery is fairly full, so it's not a low-power situation.

I think the issue occurs as follows:

Workarounds

The first work around I tried was adding the "background processing" capability to the app. This does not work.

The other strategies for background execution don't seem well-suited to the backgroundTaskEntrypoint.

Other flutter plugins could implement one of those strategies. Example: the background_fetch package.

Other ideas? Things I've missed?

It might make sense to simply document the limitations, I can send a pull request for the docs if that makes sense.

Minimal reproduction project

I'm feeling a bit lazy, haven't made a minimal example. Modifying the example app to call a method like this would do it:

    _setTimer() {
         print('Timer fired: ${DateTime.now()}\n');
         Timer(Duration(seconds, 1), _setTimer);
    }

The timer will fire every second, until the screen goes dark. Then iOS will suspend the app, and the timer fires after several seconds, or not at all.

Runtime Environment (please complete the following information if relevant):

Flutter SDK version

[✓] Flutter (Channel stable, 1.20.3, on Mac OS X 10.15.6 19G2021, locale en-US)

[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.1)
[✓] Xcode - develop for iOS and macOS (Xcode 11.7)
[✓] Android Studio (version 4.0)
[✓] IntelliJ IDEA Ultimate Edition (version 2019.3.5)
[✓] IntelliJ IDEA Community Edition (version 2017.2.2)
github-actions[bot] commented 4 years ago

This issue was automatically closed because it did not follow the issue template.

ryanheise commented 4 years ago

Reopening. I'd like to understand this issue a bit more, though. I'm guessing this issue happens only if you try to set a timer while audio is not playing? A reproduction project would definitely help me to understand the exact issue.

speaking-in-code commented 4 years ago

I'll come up with a repro project. You'll need a real iOS device to see it happen, or I can send you logs.

If you set a timer while audio is playing, the timer might not fire on time. The app can be suspended even while audio plays in the background. My guess is that when the audio completes, the app will be unsuspended... at which point your timer will fire, after much longer than intended.

I'm not sure what happens if the app is killed while suspended. The docs say it can happen, which would be interesting... but the docs don't say anything about how to design your app so that it can recover gracefully after being killed. Maybe it's pretty rare to have the app killed outright.

I'm playing around with a workaround where for small delays (e.g. 10-20 seconds), I play a silent clip of audio, and chain the callback off of audio_service.play().

ryanheise commented 4 years ago

The silent audio clip is a workaround I've used sometimes on Android to keep the media session alive. If I understand correctly, the issue seems to relate to these small delays. In any case, I look forward to looking at your reproduction project to try to get to the bottom of this.

speaking-in-code commented 4 years ago

Reproduced in this example app: https://github.com/speaking-in-code/ios_audio_service.

To use the app:

If everything works, the cowbell dings every 20 seconds.

Instead, it dings after 20 seconds, and then pauses for much longer, until you unlock the device. The screen looks like this:

sleep

ryanheise commented 4 years ago

Possibly related: https://github.com/ryanheise/audio_session/issues/7

speaking-in-code commented 4 years ago

Agreed, these are closely connected. For my app, here is what I've settled on: