InvisibleWrench / FlutterMidiCommand

A Flutter plugin to send and receive MIDI
BSD 3-Clause "New" or "Revised" License
95 stars 50 forks source link

Potential android.media.midi bug sending SysEx over BLE #72

Open ragtimekeys opened 1 year ago

ragtimekeys commented 1 year ago

Reproduce:

Everything works over USB to the device, but not BLE.

Hoping that what I've experienced is related to the android.media.midi bug described here. They mentioned on December 23rd that it's fixed and "will be available in a future build".

This link above says less than 18 or 19 bytes should be working, but for me it's not. I tried breaking it up into chunks of 10 or less bytes.

Every now and then, I'd say about 10% of the time I fresh open my app, I can send data and it does work. When it does work, the BLE midi device shows up twice in the list of devices. Very strange.

mortenboye commented 1 year ago

Hi @ragtimekeys, thanks for reporting this issue.

What device and Android version are you testing on?

I do see some issues on my side with a Pixel 6a and Android 13, but it seems im not the only one struggling with that: https://issuetracker.google.com/issues/242755161 - The linked issue is about bonded devices, but scrolling through the comments it's clear that there are a number of issues there.

The issue you've linked seems very likely to be the cause of the problems you are having. I'm currently testing with a Samsung phone running Android 12 and that works as intended.

Regarding the MIDI device appearing twice in the list, is something I am also seeing, but only on my Pixel6a/A13. Somehow the Android MidiManager hangs on to previous devices.

mortenboye commented 1 year ago

Hi @ragtimekeys, if you are still having issues you can try to run with this branch: https://github.com/InvisibleWrench/FlutterMidiCommand/tree/selfcontained-ble-midi It handles BLE devices without android.media.midi, which is much more robust in my testing.

ragtimekeys commented 1 year ago

Hi @mortenboye sorry for the late reply. After extensive testing with both android.media.midi and your new branch, here's what I've discovered.

I think general SysEx sending and receiving do work for the most part so hopefully that was a OS issue. Sometimes if I send things too quickly, aka less than 150 ms apart, there can be hangs, but yeah pretty good for the most part!

Let me know if you'd like this to be listed as a separate issue.

The selfcontained-ble-midi is a good solution for reliably connecting over BLE. Unfortunately the downside for this implementation is that when you connect a BLE device through a flutter app, that device won't show up on any other apps like bandlab or a synth app. From what I've seen, FlutterMidiCommand's iOS implementation has the same issue unless a developer opens the native Apple's CABTMIDICentralViewController menu from within a flutter application.

The android.media.midi properly adds the BLE device to the entire android OS, so to speak, since that device will show up on multiple other apps like Bandlab.

Both the master branch and selfcontained-ble-midi have the next issue I will elaborate on: Let's say you use the "Bluetooth MIDI Connect" App to connect to a BLE device. Then open your flutter app and attempt to connect to the device that appears as a "native" device since it was already connected over bluetooth. I am unable to actually connect past that point. The errors that I get are below (from the master branch version).

Thank you so much for working on Android implementation!

I/flutter (12290): changeScreen called
I/flutter (12290): state = AppLifecycleState.inactive
I/flutter (12290): state = AppLifecycleState.paused
D/FlutterMIDICommand(12290): MIDI device added MidiDeviceInfo[mType=3,mInputPortCount=1,mOutputPortCount=1,mProperties=Bundle[{name=JSM05908, bluetooth_device=FF:73:69:00:59:08}],mIsPrivate=false
I/flutter (12290): setup changed deviceFound
I/flutter (12290): state = AppLifecycleState.resumed
D/FlutterMIDICommand(12290): Stop BLE Scan
I/flutter (12290): CONNECTION SCREEN INIT STATE
D/FlutterMIDICommand(12290): add device from midiManager id FF:73:69:00:59:08
D/FlutterMIDICommand(12290): add native device FF:73:69:00:59:08 type 3
D/FlutterMIDICommand(12290): list {FF:73:69:00:59:08={name=JSM05908, id=FF:73:69:00:59:08, type=native, connected=false, inputs=[{id=0, connected=false}], outputs=[{id=0, connected=false}]}}
I/flutter (12290): start ble central
D/FlutterMIDICommand(12290): tryToInitBT
D/FlutterMIDICommand(12290): Already permitted
D/FlutterMIDICommand(12290): Stop BLE Scan
D/FlutterMIDICommand(12290): Start BLE Scan
I/flutter (12290): wait for init
D/FlutterMIDICommand(12290): FlutterStreamHandler onListen
I/flutter (12290): BluetoothState.unknown
I/flutter (12290): bluetooth state change BluetoothState.poweredOn
I/flutter (12290): bluetooth state change BluetoothState.poweredOn
I/flutter (12290): done
D/FlutterMIDICommand(12290): Stop BLE Scan
D/FlutterMIDICommand(12290): Start BLE Scan
D/FlutterMIDICommand(12290): connectedGattDevice FF:73:69:00:59:08 type 2 name JSM05908
D/FlutterMIDICommand(12290): add bonded device DC:2C:26:41:26:97 type 1 name Premium Pedal
D/FlutterMIDICommand(12290): add device from midiManager id FF:73:69:00:59:08
D/FlutterMIDICommand(12290): add native device FF:73:69:00:59:08 type 3
D/FlutterMIDICommand(12290): list {FF:73:69:00:59:08={name=JSM05908, id=FF:73:69:00:59:08, type=native, connected=false, inputs=[{id=0, connected=false}], outputs=[{id=0, connected=false}]}}
I/flutter (12290): connect
D/FlutterMIDICommand(12290): connect to native device: FF:73:69:00:59:08
D/FlutterMIDICommand(12290): not found device []
E/flutter (12290): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(ERROR, Device not found, null, null)
E/flutter (12290): #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:653)
E/flutter (12290): #1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:315)
E/flutter (12290): <asynchronous suspension>
E/flutter (12290): 
ragtimekeys commented 1 year ago

Regarding the native device bug, I discovered that it is due to the filter line in FlutterMidiCommandPlugin.kt

val devices = midiManager.devices.filter { d -> d.id.toString() == deviceId }

Since that filtered the array to a size of 0, there's probably several ways to get the right device from that list, I can try some things soon.