You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter/Dart:异步递归函数中await未等待问题求助

问题分析

你的代码核心问题在于playNextPlayer方法没有等待播放器完成就提前返回,导致后续的await ourSoundRecorder.stop()直接执行:

  • positionStream.listen()是订阅流的异步操作,不会阻塞方法执行,playNextPlayer在启动第一个播放器后就立刻完成返回。
  • 递归调用放在流监听的回调里,这个回调的异步逻辑不会影响playNextPlayer主方法的完成时机,整个播放序列没有形成完整的等待链。
修复方案

我们可以利用AudioPlayer的onPlayerComplete流(播放完成时触发),将其转换为可等待的Future,确保每个播放器播放完成后再启动下一个,让整个播放序列形成完整的异步等待链。

基础版(顺序播放,无重叠)

if (isDrumsSelected) {
    final List<AudioPlayer> drumBeatsAudioPlayersList = [];

    for (int i = 0; i < numberOfBars; i++) {
        drumBeatsAudioPlayersList.add(AudioPlayer());
        await drumBeatsAudioPlayersList[i].setAsset('assets/drum_beats/$drumBeatsFolderSelected/${bpmSliderValue.toInt()}BPM.mp3');
    }

    int n = 0;
    
    Future<void> playNextPlayer(int index) async {
        if (index >= numberOfBars) return;

        // 启动当前播放器
        drumBeatsAudioPlayersList[index].play();
        
        // 等待当前播放器播放完成
        await drumBeatsAudioPlayersList[index].onPlayerComplete.first;
        
        // 递归播放下一个
        await playNextPlayer(index + 1);
    }

    await playNextPlayer(n); // 等待整个播放序列完成

    /// Stop the recording(& music) and...
    await ourSoundRecorder.stop();

    /// ...play the recording.
    await recordingPlayer.setFilePath(filePath);
    await recordingPlayer.setLoopMode(LoopMode.all);
    await recordingPlayer.play();
}

进阶版(支持提前启动下一个播放器,保留重叠逻辑)

如果需要保留原有的“提前110ms启动下一个播放器”的重叠逻辑,可以调整为以下写法:

if (isDrumsSelected) {
    final List<AudioPlayer> drumBeatsAudioPlayersList = [];

    for (int i = 0; i < numberOfBars; i++) {
        drumBeatsAudioPlayersList.add(AudioPlayer());
        await drumBeatsAudioPlayersList[i].setAsset('assets/drum_beats/$drumBeatsFolderSelected/${bpmSliderValue.toInt()}BPM.mp3');
    }

    int n = 0;
    
    Future<void> playNextPlayer(int index) async {
        if (index >= numberOfBars) return;

        final currentPlayer = drumBeatsAudioPlayersList[index];
        currentPlayer.play();

        // 提前启动下一个播放器的逻辑
        final positionSubscription = currentPlayer.positionStream.listen((position) {
            final duration = currentPlayer.duration;
            if (duration != null) {
                final remaining = duration - position;
                const timeThreshold = Duration(milliseconds: 110);
                if (remaining <= timeThreshold && index + 1 < numberOfBars) {
                    drumBeatsAudioPlayersList[index + 1].play();
                    positionSubscription.cancel(); // 避免重复触发
                }
            }
        });

        // 等待当前播放器播放完成,再递归处理下一个
        await currentPlayer.onPlayerComplete.first;
        await playNextPlayer(index + 1);
    }

    await playNextPlayer(n); // 等待所有播放器播放完成

    await ourSoundRecorder.stop();

    await recordingPlayer.setFilePath(filePath);
    await recordingPlayer.setLoopMode(LoopMode.all);
    await recordingPlayer.play();
}
修复核心点
  • 每个playNextPlayer方法都会等待当前播放器的onPlayerComplete事件,确保当前播放器播放完成后再进入下一轮递归。
  • 整个递归链形成完整的异步等待关系,只有最后一个播放器播放完成,await playNextPlayer(n)才会结束,进而执行录音停止逻辑。

内容的提问来源于stack exchange,提问作者bbr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 14:53:22