Skip to content

Isolates are never terminated/killed after stop #291

@jakubdibala

Description

@jakubdibala

@ryanheise I'm experiencing an issue with multiple isolates running in version 0.7.2. When I start AudioService, two isolates are created:

  • _flutterIsolateEntryPoint
  • _backgroundCallbackDispatcher

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:

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!

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions