ryanheise / audio_session

MIT License
118 stars 83 forks source link

Exception in AndroidAudioManager.getMicrophones() #64

Closed toebgen closed 2 years ago

toebgen commented 2 years ago

First of all, thanks a lot for this great package, I am really a big fan!

While playing around with the AndroidAudioManager I noticed that the getMicrophones() function throws an exception. I am well aware that it is marked as "UNTESTED". Anyways, it probably makes sense to note some test results here.

API level on my device: 30. Flutter:

[✓] Flutter (Channel stable, 3.0.1, on macOS 12.4 21F79 darwin-arm)
[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)

The error message I receive is:

E/flutter ( 5854): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: type '_InternalLinkedHashMap<Object?, Object?>' is not a subtype of type 'Map<String, dynamic>' in type cast
E/flutter ( 5854): #0      _CastListBase.[] (dart:_internal/cast.dart:99:46)
E/flutter ( 5854): #1      ListMixin.elementAt (dart:collection/list.dart:78:33)
E/flutter ( 5854): #2      MappedListIterable.elementAt (dart:_internal/iterable.dart:413:40)
E/flutter ( 5854): #3      ListIterator.moveNext (dart:_internal/iterable.dart:342:26)
E/flutter ( 5854): #4      new _GrowableList._ofEfficientLengthIterable (dart:core-patch/growable_array.dart:189:27)
E/flutter ( 5854): #5      new _GrowableList.of (dart:core-patch/growable_array.dart:150:28)
E/flutter ( 5854): #6      new List.of (dart:core-patch/array_patch.dart:51:28)
E/flutter ( 5854): #7      ListIterable.toList (dart:_internal/iterable.dart:213:44)
E/flutter ( 5854): #8      AndroidAudioManager.getMicrophones

Minimal example:

import 'package:audio_session/audio_session.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => const MaterialApp(
        home: MyHomePage(),
      );
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
    AudioSession.instance.then((audioSession) async {
      await audioSession.configure(const AudioSessionConfiguration.music());
    });
  }

  Future<void> _getMicrophones() async {
    final androidAudioManager = AndroidAudioManager();
    final microphones = await androidAudioManager.getMicrophones();
    if (kDebugMode) {
      print('${microphones.length} microphones found');
    }
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        body: const Center(
          child: Text('Get microphones'),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: _getMicrophones,
          child: const Icon(Icons.mic_external_on_rounded),
        ),
      );
}
ryanheise commented 2 years ago

Thanks for reporting. Let's see:

  /// (UNTESTED) Requires API level 28
  Future<List<AndroidMicrophoneInfo>> getMicrophones() async {
    return ((await _channel.invokeListMethod<Map<String, dynamic>>(
            'getMicrophones')) as List<dynamic>)
        .map((raw) => AndroidMicrophoneInfo(
              description: raw['description'],
              id: raw['id'],
              type: raw['type'],
              address: raw['address'],
              location: decodeEnum(
                  AndroidMicrophoneLocation.values, raw['location'],
                  defaultValue: AndroidMicrophoneLocation.unknown),
              group: raw['group'],
              indexInTheGroup: raw['indexInTheGroup'],
              position: (raw['position'] as List<dynamic>).cast<double>(),
              orientation: (raw['orientation'] as List<dynamic>).cast<double>(),
              frequencyResponse: (raw['frequencyResponse'] as List<dynamic>)
                  .map((dynamic item) => (item as List<dynamic>).cast<double>())
                  .toList(),
              channelMapping: (raw['channelMapping'] as List<dynamic>)
                  .map((dynamic item) => (item as List<dynamic>).cast<int>())
                  .toList(),
              sensitivity: raw['sensitivity'],
              maxSpl: raw['maxSpl'],
              minSpl: raw['minSpl'],
              directionality: decodeEnum(
                  AndroidMicrophoneDirectionality.values, raw['directionality'],
                  defaultValue: AndroidMicrophoneDirectionality.unknown),
            ))
        .toList();
  }

My guess is that invokeListMethod doesn't do casts recursively, each item inside the list must be explicitly converted via .cast(). I can't fix it right now, but will take a look on the weekend.

ryanheise commented 2 years ago

I've pushed a fix on the fix/microphone-cast branch, please let me know if it works.

ryanheise commented 2 years ago

FYI I plan to release this fix within the next day, so it will be good if you are able to test before then if you want to ensure the next release contains a working fix for you.

ryanheise commented 2 years ago

This is now published, so I'll close the issue. I can reopen if you later if you find it not working.