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

iOS端Flutter Sound录音异常:生成空文件且播放失败

Flutter Sound iOS端录音生成空文件及播放失败问题

我使用Flutter Sound开发录音功能,Android端运行正常,但iOS端无报错却生成空录音文件,播放时触发错误:

PlatformException (PlatformException(Audio Player, startPlayer failure, null, null))

已在Info.plist中配置权限:

<key>NSMicrophoneUsageDescription</key>
<string>We need access to your microphone to record audio.</string>
<key>NSDocumentsFolderUsageDescription</key>
<string>We need access to save your recordings.</string>

尝试更换Codec.pcm16WAV等编码格式,在iOS真机测试问题依旧,相关核心代码如下:

class RecordYourNameScreen extends ConsumerStatefulWidget {
  const RecordYourNameScreen({super.key});

  @override
  _RecordYourNameScreenState createState() => _RecordYourNameScreenState();
}

class _RecordYourNameScreenState extends ConsumerState<RecordYourNameScreen>
    with SingleTickerProviderStateMixin {
  bool isRecordingDone = false;
  bool isRecording = false;
  late AnimationController _controller;
  late Animation<double> _animation;
  FlutterSoundRecorder? _recorder;
  String? _path;
  bool isLoading = false;

  @override
  void initState() {
    super.initState();
    requestPermissions();
    _recorder = FlutterSoundRecorder();
    initRecorder();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    )..repeat(reverse: true);

    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
  }

  Future<void> requestPermissions() async {
    Map<Permission, PermissionStatus> statuses = await [
      Permission.microphone,
      Permission.storage,
    ].request();

    if (statuses[Permission.microphone] != PermissionStatus.granted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Microphone permission is required to record audio')),
      );
      return;
    }

    if (statuses[Permission.storage] != PermissionStatus.granted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Storage permission is required to save recordings')),
      );
      return;
    }
  }

  Future<void> initRecorder() async {
    await _recorder!.openRecorder();
  }

  Future<void> startRecording() async {
    var status = await Permission.microphone.request();
    if (status == PermissionStatus.granted) {
      zlog.v("startRecording");
      final directory = await getApplicationDocumentsDirectory();
      _path = '${directory.path}/audio_example.aac';

      await _recorder!.startRecorder(
        toFile: _path,
        codec: Codec.aacADTS,
      );
      setState(() {
        isRecording = true;
      });
    } else {
      zlog.v("startRecording failed ${status.name}");
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to start recording - ${status.name}')),
      );
    }
  }

  Future<void> stopRecording() async {
    await _recorder!.stopRecorder();
    setState(() {
      isRecording = false;
      isRecordingDone = true;
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  // 其余代码省略
}

解决方法

1. 调整权限与录音器初始化顺序

当前在initState中同时请求权限和初始化录音器,可能导致权限未通过时录音器已初始化,iOS上会限制录音。修改为权限通过后再初始化录音器:

@override
void initState() {
  super.initState();
  _controller = AnimationController(
    vsync: this,
    duration: const Duration(seconds: 1),
  )..repeat(reverse: true);
  _animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
  // 先请求权限,再初始化录音器
  requestPermissions();
}

Future<void> requestPermissions() async {
  Map<Permission, PermissionStatus> statuses = await [
    Permission.microphone,
    Permission.storage,
  ].request();

  if (statuses[Permission.microphone] != PermissionStatus.granted) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Microphone permission is required to record audio')),
    );
    return;
  }

  if (statuses[Permission.storage] != PermissionStatus.granted) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Storage permission is required to save recordings')),
    );
    return;
  }

  // 权限通过后初始化录音器
  _recorder = FlutterSoundRecorder();
  await initRecorder();
}

2. 适配iOS编码与文件后缀

iOS对aacADTS格式的兼容性较差,建议使用Codec.aacMP4,并将文件后缀改为.m4a:

Future<void> startRecording() async {
  var status = await Permission.microphone.request();
  if (status == PermissionStatus.granted) {
    zlog.v("startRecording");
    final directory = await getApplicationDocumentsDirectory();
    // 修改后缀为.m4a,对应aacMP4编码
    _path = '${directory.path}/audio_example.m4a';

    await _recorder!.startRecorder(
      toFile: _path,
      codec: Codec.aacMP4, // 使用iOS兼容的编码
    );
    setState(() {
      isRecording = true;
    });
  } else {
    zlog.v("startRecording failed ${status.name}");
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Failed to start recording - ${status.name}')),
    );
  }
}

3. 完善录音器资源释放

在dispose中关闭录音器,避免资源泄漏:

@override
void dispose() {
  _controller.dispose();
  // 关闭录音器
  _recorder?.closeRecorder();
  _recorder = null;
  super.dispose();
}

4. 验证文件存在逻辑

确保checkIfFileExists方法正确判断文件存在,可替换为原生File API:

Future<bool> checkIfFileExists(String path) async {
  return await File(path).exists();
}

5. 增加录音状态校验

在startRecording前检查录音器是否已打开,避免重复初始化:

Future<void> startRecording() async {
  var status = await Permission.microphone.request();
  if (status != PermissionStatus.granted) {
    zlog.v("startRecording failed ${status.name}");
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Failed to start recording - ${status.name}')),
    );
    return;
  }

  // 检查录音器状态
  if (_recorder == null || !_recorder!.isOpen) {
    _recorder = FlutterSoundRecorder();
    await initRecorder();
  }

  zlog.v("startRecording");
  final directory = await getApplicationDocumentsDirectory();
  _path = '${directory.path}/audio_example.m4a';

  await _recorder!.startRecorder(
    toFile: _path,
    codec: Codec.aacMP4,
  );
  setState(() {
    isRecording = true;
  });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 18:14:51