Noise Cancellation in Flutter causing Error for Android

Hello! I’m new to the LiveKit ecosystem and while implementing the Noise Cancellation module for the flutter SDK I ran into a bug that already had an issue open with no response from the devs yet. I’m hoping to get some clarification on expectations for cases like this, are updates frequent and what is the best way to interface for support? I started in Slack and got redirected here.

This is the link to the issue I am referring to, any help is very appreciated: [bug] noise cancellation crashes · Issue #998 · livekit/client-sdk-flutter · GitHub

Hey there! Are you having this issue only with Krisp noise cancellation or also with ai-coustics?

It’s Krisp noise cancellation provided with the https://pub.dev/packages/livekit_noise_filter package

1 Like

Is this exactly the same issue as [bug] noise cancellation crashes · Issue #998 · livekit/client-sdk-flutter · GitHub ? I.e. `I/flutter (12724): VoiceAIScreen: Initialize failed: PlatformException(INVALID_ARGUMENT, track is not LocalAudioTrack, null, null)`

Is this reproducible with any of the minimal examples, such as client-sdk-flutter/example at main · livekit/client-sdk-flutter · GitHub ?

Hi Darryn! Thank you for the reply and thank you for any assistance you can offer.

I’m currently unable to reproduce in the example project, it seems to just work as expected; I can see the processor when calling setProcessor however in my app I’m getting the ‘Reply already submitted’ exception. I tried both in-line room options processor like the livekit_noise_filter shows as well as setting it after while using FastConnectOptions like the example code.

I must be missing something obvious… When I remove the processor in room options I’m able to connect and everything works.

For note, I’m on Flutter 3.38.7 and livekit_client: 2.7.0

Here is the exception I’m looking at:

E/MethodChannel#livekit_krisp_noise_filter(29712): Failed to handle method call
E/MethodChannel#livekit_krisp_noise_filter(29712): java.lang.IllegalStateException: Reply already submitted
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at io.flutter.embedding.engine.dart.DartMessenger$Reply.reply(DartMessenger.java:431)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler$1.success(MethodChannel.java:272)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at io.livekit.livekit_noise_filter.LiveKitKrispNoiseFilterPlugin.init(LiveKitKrispNoiseFilterPlugin.kt:41)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at io.livekit.livekit_noise_filter.LiveKitKrispNoiseFilterPlugin.onMethodCall(LiveKitKrispNoiseFilterPlugin.kt:56)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:267)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:292)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:319)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at android.os.Handler.handleCallback(Handler.java:1070)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at android.os.Handler.dispatchMessage(Handler.java:125)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at android.os.Looper.dispatchMessage(Looper.java:333)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at android.os.Looper.loopOnce(Looper.java:263)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at android.os.Looper.loop(Looper.java:367)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at android.app.ActivityThread.main(ActivityThread.java:9287)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at java.lang.reflect.Method.invoke(Native Method)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:566)
E/MethodChannel#livekit_krisp_noise_filter(29712): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:929)
E/DartMessenger(29712): Uncaught exception in binary message listener
E/DartMessenger(29712): java.lang.IllegalStateException: Reply already submitted
E/DartMessenger(29712): 	at io.flutter.embedding.engine.dart.DartMessenger$Reply.reply(DartMessenger.java:431)
E/DartMessenger(29712): 	at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:287)
E/DartMessenger(29712): 	at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:292)
E/DartMessenger(29712): 	at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:319)
E/DartMessenger(29712): 	at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
E/DartMessenger(29712): 	at android.os.Handler.handleCallback(Handler.java:1070)
E/DartMessenger(29712): 	at android.os.Handler.dispatchMessage(Handler.java:125)
E/DartMessenger(29712): 	at android.os.Looper.dispatchMessage(Looper.java:333)
E/DartMessenger(29712): 	at android.os.Looper.loopOnce(Looper.java:263)
E/DartMessenger(29712): 	at android.os.Looper.loop(Looper.java:367)
E/DartMessenger(29712): 	at android.app.ActivityThread.main(ActivityThread.java:9287)
E/DartMessenger(29712): 	at java.lang.reflect.Method.invoke(Native Method)
E/DartMessenger(29712): 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:566)
E/DartMessenger(29712): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:929)

Which appears when I try to set the processor on the audio track, here is the code snippet, this throws at the last line.

      _noiseFilter = LiveKitNoiseFilter();

      final room = Room(
        roomOptions: RoomOptions(
          defaultAudioCaptureOptions: AudioCaptureOptions(
            processor: _noiseFilter,
          ),
          defaultAudioPublishOptions: const AudioPublishOptions(
            name: 'microphone',
          ),
        ),
      );
      _registerRoomListeners(room);

      try {
        await room.connect(
          wsUrl,
          token,
          connectOptions: const ConnectOptions(autoSubscribe: false),
        );
      } catch (e) {
        setIsConnecting(false);
        FirebaseCrashlytics.instance
            .recordError(e, StackTrace.current, reason: 'LiveKit connect failed');
        return 'Failed to connect to voice channel';
      }

      await room.localParticipant?.setMicrophoneEnabled(true);

I also noticed the example uses FastConnectOptions so I tried with that and get the same error when calling await _audioTrack?.setProcessor(_noiseFilter); Here is that stack track:

E/MethodChannel#livekit_krisp_noise_filter(32549): Failed to handle method call
E/MethodChannel#livekit_krisp_noise_filter(32549): java.lang.IllegalStateException: Reply already submitted
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at io.flutter.embedding.engine.dart.DartMessenger$Reply.reply(DartMessenger.java:431)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler$1.success(MethodChannel.java:272)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at io.livekit.livekit_noise_filter.LiveKitKrispNoiseFilterPlugin.init(LiveKitKrispNoiseFilterPlugin.kt:41)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at io.livekit.livekit_noise_filter.LiveKitKrispNoiseFilterPlugin.onMethodCall(LiveKitKrispNoiseFilterPlugin.kt:56)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:267)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:292)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:319)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at android.os.Handler.handleCallback(Handler.java:1070)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at android.os.Handler.dispatchMessage(Handler.java:125)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at android.os.Looper.dispatchMessage(Looper.java:333)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at android.os.Looper.loopOnce(Looper.java:263)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at android.os.Looper.loop(Looper.java:367)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at android.app.ActivityThread.main(ActivityThread.java:9287)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at java.lang.reflect.Method.invoke(Native Method)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:566)
E/MethodChannel#livekit_krisp_noise_filter(32549): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:929)
E/DartMessenger(32549): Uncaught exception in binary message listener
E/DartMessenger(32549): java.lang.IllegalStateException: Reply already submitted
E/DartMessenger(32549): 	at io.flutter.embedding.engine.dart.DartMessenger$Reply.reply(DartMessenger.java:431)
E/DartMessenger(32549): 	at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:287)
E/DartMessenger(32549): 	at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:292)
E/DartMessenger(32549): 	at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:319)
E/DartMessenger(32549): 	at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
E/DartMessenger(32549): 	at android.os.Handler.handleCallback(Handler.java:1070)
E/DartMessenger(32549): 	at android.os.Handler.dispatchMessage(Handler.java:125)
E/DartMessenger(32549): 	at android.os.Looper.dispatchMessage(Looper.java:333)
E/DartMessenger(32549): 	at android.os.Looper.loopOnce(Looper.java:263)
E/DartMessenger(32549): 	at android.os.Looper.loop(Looper.java:367)
E/DartMessenger(32549): 	at android.app.ActivityThread.main(ActivityThread.java:9287)
E/DartMessenger(32549): 	at java.lang.reflect.Method.invoke(Native Method)
E/DartMessenger(32549): 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:566)
E/DartMessenger(32549): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:929)
I/flutter (32549): ----------------FIREBASE CRASHLYTICS----------------
I/flutter (32549): PlatformException(INVALID_ARGUMENT, track is not LocalAudioTrack, null, null)
I/flutter (32549): #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:653:7)
I/flutter (32549): #1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:367:18)
I/flutter (32549): <asynchronous suspension>
I/flutter (32549): #2      LiveKitNoiseFilter.init (package:livekit_noise_filter/src/livekit_noise_filter_native.dart:24:5)
I/flutter (32549): <asynchronous suspension>
I/flutter (32549): #3      LocalTrack.setProcessor (package:livekit_client/src/track/local/local.dart:261:5)

And that piece of code:

      final room = Room(
        roomOptions: RoomOptions(
          adaptiveStream: true,
          dynacast: true,
          defaultAudioCaptureOptions: AudioCaptureOptions(
            processor: _noiseFilter,
          ),
          defaultAudioPublishOptions: const AudioPublishOptions(
            name: 'microphone',
          ),
        ),
      );
      _registerRoomListeners(room);

      try {
        await room.prepareConnection(wsUrl, token);
        await room.connect(
          wsUrl,
          token,
          connectOptions: const ConnectOptions(autoSubscribe: false),
          fastConnectOptions: FastConnectOptions(
            microphone: TrackOption(track: _audioTrack),
          ),
        );
      } catch (e) {
        await _audioTrack?.stop();
        _audioTrack = null;
        setIsConnecting(false);
        FirebaseCrashlytics.instance
            .recordError(e, StackTrace.current, reason: 'LiveKit connect failed');
        return 'Failed to connect to voice channel';
      }

      await room.localParticipant?.setMicrophoneEnabled(willPublishMic);
      if (_audioTrack != null) {
        await _audioTrack!.setProcessor(_noiseFilter!);
      }

image

If you initialize the plugin twice (define in options and call setProcessor), that will trigger the ‘Reply already submitted’ exception.

It sounds like a cop-out but the ‘solution’ in the original bug you referenced is probably the most straight forward for your issue, especially if you don’t see the issue in the sample apps, “Cloned the repo and got claude code to fix it“. It is very important to follow the steps at Coding agent support and tools | LiveKit Documentation before trying to resolve the issue with any coding agent, otherwise any answer you receive will be sub-par.

Like you say, I suspect this is something obvious (as all bugs are after you understand them) but without full visibility to your app, it’s difficult to say.