@ryanheise I'm experiencing an issue with multiple isolates running in version 0.7.2. When I start AudioService, two isolates are created:
When I call stop on AudioService, future is completed and isolates should be terminated/killed. But they are still running. Now I call start again and two new isolates are running, see screenshot:
![]()
Entrypoint:
static void initBackgroundIsolate() {
AudioServiceBackground.run(() => MediaBackgroundTask());
}
class MediaBackgroundTask extends BackgroundAudioTask {
/// Future completer
final Completer _completer = Completer();
/// Audio player instance
final AudioPlayer _audioPlayer = AudioPlayer();
/// Playable media item
MediaItem _media;
/// Is media playing?
bool _playing = false;
/// Stream subscriptions from audio player
StreamSubscription _maxDurationSub;
StreamSubscription _positionChangedSub;
/// Initializes media playback
@override
Future<void> onStart() async {
log("Starting background isolate...");
/// Broadcast init state with controls
log("Setting state to buffering");
AudioServiceBackground.setState(
controls: [stopControl],
basicState: BasicPlaybackState.buffering,
systemActions: [MediaAction.stop],
);
/// Wait until thread completes
await _completer.future;
log("Isolate lifecycle end!");
/// Cancel all subscriptions
_maxDurationSub?.cancel();
_positionChangedSub?.cancel();
// Release media player
log("Releasing media player resources");
_audioPlayer.release();
}
/// Handler for pause action
@override
void onPause() async {
/// Pause audio playback
log("PAUSE --> pausing playback");
await _audioPlayer.pause();
/// Notify that we are in paused state with controls
log("PAUSE --> Setting state to paused");
await AudioServiceBackground.setState(
controls: [playControl],
basicState: BasicPlaybackState.paused,
systemActions: [MediaAction.play],
);
}
/// Handler for adding queue item
@override
void onAddQueueItem(MediaItem mediaItem) async {
/// Set media item
_media = mediaItem;
await AudioServiceBackground.setMediaItem(mediaItem);
log("ON_ADD_QUEUE_ITEM --> Setting media item to ${mediaItem.title}");
/// Prepare audio player handling
if (!mediaItem.extras["is_stream"]) {
_maxDurationSub = _audioPlayer.onDurationChanged.listen((Duration p) async {
log("ON_ADD_QUEUE_ITEM --> Track duration loaded: $p");
/// Create new media item
_media = _media.copyWith(duration: p.inMilliseconds);
await AudioServiceBackground.setMediaItem(_media);
/// Send message to UI
SendPort sendPort = IsolateNameServer.lookupPortByName("media_background_task:msg_api");
sendPort?.send({
"type": "track_duration_loaded",
"data": {
"duration_milliseconds": p.inMilliseconds,
}
});
});
_positionChangedSub = _audioPlayer.onAudioPositionChanged.listen((Duration p) async {
log("ON_ADD_QUEUE_ITEM --> Audio position changed to $p");
// _position = p;
await AudioServiceBackground.setState(
controls: [],
basicState: AudioServiceBackground.state.basicState,
position: p.inMilliseconds,
systemActions: [
MediaAction.pause,
MediaAction.stop,
]);
});
}
// on playback complete
_audioPlayer.onPlayerCompletion.listen((event) {
log("ON_ADD_QUEUE_ITEM --> Playback is complete, calling onStop()");
onStop();
});
}
/// Handler for play action
@override
void onPlay() async {
/// If media is not playing, play
if (!_playing) {
log("PLAY --> Media is not playing, calling play() {uuid: ${_media.extras["uuid"]}}");
await _audioPlayer.play(_media.id);
_playing = true;
} else {
log("PLAY --> Media is playing, calling resume() {uuid: ${_media.extras["uuid"]}}");
await _audioPlayer.resume();
}
log("PLAY --> Setting state to playing");
await AudioServiceBackground.setState(
controls: [pauseControl],
basicState: BasicPlaybackState.playing,
systemActions: [MediaAction.pause],
);
}
/// Handler for stop action
/// This will end background task isolate.
@override
void onStop() async {
log("STOP --> Stopping playback");
/// Stop audio playback
await _audioPlayer.stop();
/// Broadcast stopped state
log("STOP --> Setting state to stopped");
await AudioServiceBackground.setState(controls: [], basicState: BasicPlaybackState.stopped);
/// End isolate life
log("STOP --> Ending background isolate life!");
_completer.complete();
}
/// Handle for seek action
@override
void onSeekTo(int position) async {
log("SEEK --> Seeking to ${Duration(milliseconds: position).formatForPlayer()}");
/// Pause playback
await _audioPlayer.pause();
/// Seek to position
await _audioPlayer.seek(Duration(milliseconds: position));
/// Resume playback
await _audioPlayer.resume();
}
/// Logging method
void log(String msg) {
debugPrint("[MEDIA_SERVICE_BACKGROUND] ---> $msg");
}
}
Is there something wrong in my code, or it is a bug in this package?
I'm running iOS 13.4, Flutter dev channel, here is output from flutter doctor:
Doctor summary (to see all details, run flutter doctor -v):
[โ] Flutter (Channel dev, v1.18.0, on Mac OS X 10.15.4 19E287, locale cs-CZ)
[โ] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[โ] Xcode - develop for iOS and macOS (Xcode 11.4.1)
[โ] Chrome - develop for the web
[โ] Android Studio (version 3.6)
[โ] VS Code (version 1.44.2)
[โ] Connected device (3 available)
โข No issues found!
Thanks!
To better help me investigate this bug, can you please fill in all fields in the bug issue template? If you are modifying the example project, please also create a fork as per the instructions (since it helps me to clearly see in the diffs what you have changed). Thanks.
@ryanheise I cloned example project in this repo and run without any modifications.
When you open observatory inspector and do following:
@ryanheise I tried running this project on stable flutter branch, issue still appearing.
Can you still fill in the bug issue template?
Describe the bug
Background isolates are never killed after Future is completed (I am using Completer same as in example).
Minimal reproduction project
Issue can be reproduced in example project in this repository.
To Reproduce
Steps to reproduce the behavior:
Call stack is filling up with every "start and stop" actions with 2 isolates per action.
Error messages
No error messages are being produced.
Expected behavior
Two isolates should be killed/removed from call stack after calling stop (or completing the future which is waiting inside onStart() method).
Screenshots
Runtime Environment (please complete the following information if relevant):
Flutter SDK version
Doctor summary (to see all details, run flutter doctor -v):
[โ] Flutter (Channel stable, v1.12.13+hotfix.9, on Mac OS X 10.15.4 19E287, locale cs-CZ)
[โ] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[โ] Xcode - develop for iOS and macOS (Xcode 11.4.1)
[โ] Android Studio (version 3.6)
[โ] VS Code (version 1.44.2)
[โ] Connected device (1 available)
โข No issues found!
Additional context
Nothing.
@ryanheise Thanks.
Thanks, much appreciated. This might be an issue with flutter_isolate. This line in the plugin's dart code is supposed to do the trick for iOS:
AudioService._flutterIsolate?.kill();
Will have to check.
I've just committed a fix (hopefully) to git master. Can you let me know if it fixes the issue on your end?
@ryanheise I'm sorry but this is not working correctly. Now when I hit stop, app crashes completely. I think that the main app thread is killed too.
And second issue is that now I don't see any _backgroundCallbackDispatcher isolate and I don't see any media controls on device (lock screen player, control center player, etc.).
Is this still on the example project?
And can you provide the error message?
@ryanheise Yes, on example project, same configuration but with your commit. No error messages are being displayed. App just crashes and in terminal I see message "Lost connection to device".
Unfortunately, my macincloud installation just broke minutes ago. While I wait for tech support, I'm not sure if you are able to perhaps assist in debugging and inserting some NSLog() statements in the code. It might be a bit complicated though, since the crash is likely happening in the flutter_isolate plugin. But if you're willing to help in that regard, I can provide instructions.
Tech support replied quicker than expected, and everything is back online again.
I don't have the simulator for your exact iOS version, but I tried to match as closely as possible by running the simulator for iOS 13.3 on an iPhone 8. Unfortunately (?) it did not crash as you described. So I wonder if this issue just happens on physical devices. Can you confirm whether the crash happens or doesn't happen on the simulator on your end?
@jakubdibala I had a similar issue.
I've noticed you are using _audioPlayer.release(), I think it was renamed to _audioPlayer.dispose() and you are supposed to await for it to finish - this caused me some pain and crashes.
Also in my case there was a problem that stop was called on AudioPlayer instance that was in AudioPlaybackState.none.
This is connected to how AudioPlayer implements stop:
Future<void> stop() async {
switch (playbackState) {
case AudioPlaybackState.stopped:
break;
case AudioPlaybackState.connecting:
case AudioPlaybackState.completed:
case AudioPlaybackState.playing:
case AudioPlaybackState.paused:
// Update local state immediately so that queries aren't surprised.
// NOTE: Android implementation already handles this.
// TODO: Do the same for iOS so the line below becomes unnecessary.
_audioPlaybackEvent = _audioPlaybackEvent.copyWith(
state: AudioPlaybackState.paused,
);
await _invokeMethod('stop');
break;
default:
throw Exception("Cannot call stop from none state");
}
}
Just wrap the call to _audioPlayer.stop():
if(_audioPlayer.playbackState != AudioPlaybackState.none) {
await _audioPlayer.stop();
}
@Drabuna Well spotted, and hopefully you are right (not 100% sure but hopeful). Just to be sure, are you able to confirm that this crash does not in fact occur in the example repo on an iPhone device?
@jakubdibala reported that the crash occurred with my repo example, but the line of code you spotted (_audioPlayer.release()) is not in the repo example, so something is fishy there, and so I should probably doubt the bug report. Regarding the naming of the release vs dispose method, the original bug report (the one that didn't provide the link to the whole project) doesn't specify what the imports and pubspec dependencies are, so it's not necessarily the same audio player plugin that I used in my example. We just don't know since the reproduction project for that first example wasn't supplied, and I have been basing my investigation this past week on the second bug report which indicated the example project.
I can't be 100% certain because I don't know what audio plugin was used, and whether the iOS implementation of stop and release look like they could potentially cause crashes. on iOS I wouldn't expect that to be the case due to the underlying iOS APIs.
I've decided I'm just going to publish the release. It's probably the fastest way to do testing :D And there are some important features that need to be released anyway, so we'll deal with this crash that way and get many eyes on it.
The one line of code I changed basically reverts #188 and I'm fairly confident this shouldn't be responsible for a crash since it didn't crash before #188 .
This is now released in 0.8.0.
@ryanheise I can confirm that app crashes only when using audioplayers package. I switched to just_audio and app no longer crashes.
Thank you for investigating this issue ๐ฅ I'm closing this issue.
Most helpful comment
This is now released in 0.8.0.