Canardoux / flutter_sound

Flutter plugin for sound. Audio recorder and player.
Mozilla Public License 2.0
880 stars 575 forks source link

[BUG]: stopRecorder producing an empty file - intermittently #1064

Open bloemy7 opened 3 months ago

bloemy7 commented 3 months ago

Flutter Sound Version :

├── flutter_sound 9.6.0
│   ├── flutter_sound_platform_interface 9.6.0
│   ├── flutter_sound_web 9.6.0
│   │   ├── flutter_sound_platform_interface...

Severity


Platforms you faced the error


Describe the bug In some cases, when calling stopRecorder, it does not produce a recorded file. The return is empty.

To Reproduce Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Logs!!!!

I'm sorry about this terrible bug here - I know it's pretty much impossible to action on it. The problem is I'm seeing these reports from users out in the wild, so I do not have access to logs. I have never been able to reproduce this locally, but some people see this extremely regularly. The weird thing is that even those people do not experience this every single time. It's intermittent. Do you have any idea of why this might be happening? What could cause the recording to not complete correctly?

Thanks a lot.


Larpoux commented 3 months ago

This is bad 👎 It will be terribly difficult to solve your issue. The only hint that I see is that your app is probably the only app reporting this issue (today!). If your app is not too complicated, perhaps we can look to it very carefully. Could you send a snippet of your code? Possible also to add some diagnostic in your code to give some clues when unexpected situations occur and locate a little bit sooner the problem.

Larpoux commented 3 months ago

Another thing is that if I understand correctly, your problem is both on Android and iOS. If this is true, it will mean that the problem is either in your dart code or in my dart code. This is a very good thing, if the problem is not inside the iOS or android code.

bloemy7 commented 3 months ago

Thanks a lot @Larpoux, really appreciate the fast response here. It does seem like it's both Android and iOS so that is indeed great news. Let me share the snippets that matter:

This is the part of the code where the recording is started:

    _recorder =
        await FlutterSoundRecorder(logLevel: Level.nothing).openRecorder();
    const numberOfMillisecondsOfCallback = 10000; // 10 seconds
    await _recorder?.setSubscriptionDuration(
        const Duration(milliseconds: numberOfMillisecondsOfCallback));
    _recorder?.onProgress?.listen((e) async {
      // a number of things are done during the on progress here, removing to keep short
    });

    setState(() {
      _isCurrentlyRecording = true;
    });

    // Start recording
    Codec codec;
    String fileExtension = "";
    if (_recorder != null &&
        await _recorder!.isEncoderSupported(Codec.aacMP4)) {
      codec = Codec.aacMP4;
      fileExtension = ".m4a";
    } else if (_recorder != null &&
        await _recorder!.isEncoderSupported(Codec.opusWebM)) {
      codec = Codec.opusWebM;
      fileExtension = ".opus";
    } else {
      await widget.errorWhileRecording();
      return;
    }

    if (!kIsWeb) {
      final session = await AudioSession.instance;
      await session.configure(AudioSessionConfiguration(
        avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
        avAudioSessionCategoryOptions:
            AVAudioSessionCategoryOptions.allowBluetooth |
                AVAudioSessionCategoryOptions.defaultToSpeaker,
        avAudioSessionMode: AVAudioSessionMode.spokenAudio,
        avAudioSessionRouteSharingPolicy:
            AVAudioSessionRouteSharingPolicy.defaultPolicy,
        avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none,
        androidAudioAttributes: const AndroidAudioAttributes(
          contentType: AndroidAudioContentType.speech,
          flags: AndroidAudioFlags.none,
          usage: AndroidAudioUsage.voiceCommunication,
        ),
        androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
        androidWillPauseWhenDucked: true,
      ));
      session.interruptionEventStream.listen((event) async {
        if (event.begin) {
          switch (event.type) {
            case AudioInterruptionType.pause:
            case AudioInterruptionType.unknown:
              await widget.shouldStopRecording();
              break;
            case AudioInterruptionType.duck:
              // we don't want to pause if we were just asked to duck
              break;
          }
        } else {
          switch (event.type) {
            case AudioInterruptionType.pause:
            case AudioInterruptionType.unknown:
              await widget.shouldStartRecording();
              break;
            case AudioInterruptionType.duck:
              // nothing to do here since we did not pause
              break;
          }
        }
      });
    }

    var filePath = "";
    var uuid = Uuid();
    if (!kIsWeb) {
      Directory appDocDir = await getApplicationDocumentsDirectory();
      filePath =
          '${appDocDir.path}/${uuid.v4()}_${DateTime.now().millisecondsSinceEpoch}${fileExtension}';
    } else {
      filePath =
          '${uuid.v4()}_${DateTime.now().millisecondsSinceEpoch}${fileExtension}';
    }

    await _recorder?.startRecorder(
      codec: codec,
      toFile: filePath,
    );

and this is the part where it stops:

      String? recordedFilePath = await _recorder?.stopRecorder();

      if (recordedFilePath != null && recordedFilePath.isNotEmpty) {
        // success
      } else {
        // this is the point we get at for some users - BUG
      }
bloemy7 commented 3 months ago

I hope we can identify something here - let me know what you think @Larpoux !

Larpoux commented 3 months ago

I am going to look very carefully to your code tomorrow, and I will tell you if I have any idea of what happens.

bloemy7 commented 3 months ago

Thank you @Larpoux, appreciate it!

Larpoux commented 3 months ago

@bloemy7 : I looked to your code and I feel that the problem is not with the recorder but with the audio file management. If I am right, this is good news. There is perhaps a bug in Flutter Sound when providing the file name used by the recorder to the app.

      String? recordedFilePath = await _recorder?.stopRecorder();

I suggest that you try to use the file path you specified in :

      Directory appDocDir = await getApplicationDocumentsDirectory();
      filePath =
          '${appDocDir.path}/${uuid.v4()}_${DateTime.now().millisecondsSinceEpoch}${fileExtension}';

Also, you may use the temp directory instead getApplicationDocumentsDirectory(). I don't see why it should be a problem, but you can try to use :

Directory tempDir = await getTemporaryDirectory();

from the path_provider plugin.

Please tell me what is going on. I am interested by your results, wether it is success or failure.

bloemy7 commented 3 months ago

Hi @Larpoux , thanks for looking into this. So you are recommending I just try to read from the file directly instead - that is a good point actually, since it will always use that URL regardless.

I am using the non-temp directory in case upload of the audio fails, so it doesn't get cleared from the device.

I will change this and let you know if it works or not. Thanks a lot for a great package!

bloemy7 commented 3 months ago

@Larpoux I cannot say for sure yet, but it seems like this has most likely solved the issue. Almost nobody complained about this since the update anymore when it used to be every day multiple times. So this is amazing. Thank you so much - it makes a lot of sense, really appreciate your time here!

Larpoux commented 3 months ago

There is clearly a bug in Flutter Sound in this area. This part of Flutter Sound is very simple, so it should not be very difficult to fix that. I think that other Flutter Sound users complained about this issue.. Unfortunately I don't have very much time to spend on the Flutter Sound 9.x maintenance. I spend all my time on Flutter Sound 10.x that is a major rewrite of the plugin.