From 68aec91cd769b06a8ae6d8b179127cd68cf23134 Mon Sep 17 00:00:00 2001 From: Neal Sanche Date: Wed, 7 Jan 2026 21:16:52 -0700 Subject: [PATCH] fix: cancel ALSA receive subscription on disconnect to allow clean shutdown - Store StreamSubscription from _device.receivedMessages.listen() - Cancel subscription in disconnect() before closing ALSA device - Close both _setupStreamController and _rxStreamController in teardown() - Use .toList() to avoid concurrent modification during teardown Fixes app hang on Linux shutdown caused by uncancelled ALSA receive loop --- lib/flutter_midi_command_linux.dart | 64 ++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/lib/flutter_midi_command_linux.dart b/lib/flutter_midi_command_linux.dart index c60f3c9..be7bf21 100644 --- a/lib/flutter_midi_command_linux.dart +++ b/lib/flutter_midi_command_linux.dart @@ -8,15 +8,22 @@ class LinuxMidiDevice extends MidiDevice { int cardId; int deviceId; AlsaMidiDevice _device; - - LinuxMidiDevice(this._device, this.cardId, this.deviceId, String name, String type, - this._rxStreamCtrl, bool connected) - : super( - AlsaMidiDevice.hardwareId(cardId, deviceId), - name, - type, - connected, - ) { + StreamSubscription? _receivedMessagesSub; + + LinuxMidiDevice( + this._device, + this.cardId, + this.deviceId, + String name, + String type, + this._rxStreamCtrl, + bool connected, + ) : super( + AlsaMidiDevice.hardwareId(cardId, deviceId), + name, + type, + connected, + ) { // Get input, output ports var i = 0; _device.inputPorts.toList().forEach((element) { @@ -33,7 +40,7 @@ class LinuxMidiDevice extends MidiDevice { connected = true; // connect up incoming alsa midi data to our rx stream of MidiPackets - _device.receivedMessages.listen((event) { + _receivedMessagesSub = _device.receivedMessages.listen((event) { _rxStreamCtrl.add(MidiPacket(event.data, event.timestamp, this)); }); return true; @@ -44,18 +51,23 @@ class LinuxMidiDevice extends MidiDevice { } disconnect() { + _receivedMessagesSub?.cancel(); + _receivedMessagesSub = null; _device.disconnect(); connected = false; } } class FlutterMidiCommandLinux extends MidiCommandPlatform { - StreamController _rxStreamController = StreamController.broadcast(); + StreamController _rxStreamController = + StreamController.broadcast(); late Stream _rxStream; - StreamController _setupStreamController = StreamController.broadcast(); + StreamController _setupStreamController = + StreamController.broadcast(); late Stream _setupStream; - Map _connectedDevices = Map(); + Map _connectedDevices = + Map(); final List _allAlsaDevices = []; @@ -88,15 +100,19 @@ class FlutterMidiCommandLinux extends MidiCommandPlatform { "native", _rxStreamController, _connectedDevices.containsKey( - AlsaMidiDevice.hardwareId(alsMidiDevice.cardId, alsMidiDevice.deviceId)), + AlsaMidiDevice.hardwareId( + alsMidiDevice.cardId, + alsMidiDevice.deviceId, + ), + ), ), ) .toList(); } - /// Prepares Bluetooth system - @override Future startBluetoothCentral() async { + @override + Future startBluetoothCentral() async { return Future.error("Not available on linux"); } @@ -112,7 +128,10 @@ class FlutterMidiCommandLinux extends MidiCommandPlatform { /// Connects to the device. @override - Future connectToDevice(MidiDevice device, {List? ports}) async { + Future connectToDevice( + MidiDevice device, { + List? ports, + }) async { print('connect to $device'); var linuxDevice = device as LinuxMidiDevice; @@ -140,12 +159,17 @@ class FlutterMidiCommandLinux extends MidiCommandPlatform { @override void teardown() { - _connectedDevices.values.forEach((device) { + _connectedDevices.values.toList().forEach((device) { disconnectDevice(device, remove: false); }); _connectedDevices.clear(); - _setupStreamController.add("deviceDisconnected"); - _rxStreamController.close(); + if (!_setupStreamController.isClosed) { + _setupStreamController.add("deviceDisconnected"); + _setupStreamController.close(); + } + if (!_rxStreamController.isClosed) { + _rxStreamController.close(); + } } /// Sends data to the currently connected device.wmidi hardware driver name