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
相关产品推荐
相关产品推荐

