Flutter for OpenHarmony三方库适配实战:flutter_sound 音频录制与播放
音频录制与播放是移动应用的核心功能之一,用于语音消息、音乐播放器、录音笔记、语音通话等场景。在 Flutter for OpenHarmony 应用开发中,是一个功能全面的音频处理插件,提供了完整的跨平台音频录制和播放能力。flutter_sound 库为 Flutter for OpenHarmony 提供了完整的音频录制和播放能力,支持多种编码格式和丰富的控制选项,适合各种音频处理场景。核心要
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
本文基于flutter3.27.5开发
一、flutter_sound 库概述
音频录制与播放是移动应用的核心功能之一,用于语音消息、音乐播放器、录音笔记、语音通话等场景。在 Flutter for OpenHarmony 应用开发中,flutter_sound 是一个功能全面的音频处理插件,提供了完整的跨平台音频录制和播放能力。
flutter_sound 库特点
flutter_sound 库基于 Flutter 平台接口实现,提供了以下核心特性:
完整播放功能:支持从文件、URL、Asset、数据流等多种来源播放音频,支持暂停、恢复、停止、跳转等操作。
完整录制功能:支持录制到文件或数据流,支持多种音频编码格式,支持暂停和恢复录制。
多种编码格式:支持 AAC、MP3、Opus、Vorbis、FLAC、PCM、AMR 等多种音频编码格式。
播放控制:支持音量调节、播放速度控制、进度监听、播放完成回调等。
录制控制:支持采样率、声道数、比特率配置,支持分贝级别监听。
流式处理:支持从数据流播放和录制到数据流,适合实时音频处理场景。
编码格式支持对比
| 编码格式 | Android | iOS | OpenHarmony |
|---|---|---|---|
| AAC-ADTS | ✅ | ✅ | ✅ |
| AAC-MP4 | ✅ | ✅ | ✅ |
| MP3 | ✅ | ✅ | ✅ |
| Opus-OGG | ✅ | ✅ | ✅ |
| Vorbis-OGG | ✅ | ✅ | ✅ |
| FLAC | ✅ | ✅ | ✅ |
| PCM16 | ✅ | ✅ | ✅ |
| PCM16-WAV | ✅ | ✅ | ✅ |
| AMR-NB | ✅ | ✅ | ✅ (API 18+) |
| AMR-WB | ✅ | ✅ | ✅ (API 18+) |
使用场景:语音消息、音乐播放器、录音笔记、语音备忘录、播客应用、语音通话、实时音频处理等。
二、安装与配置
2.1 添加依赖
在项目的 pubspec.yaml 文件中添加 flutter_sound 依赖:
dependencies:
flutter_sound:
git:
url: https://atomgit.com/openharmony-sig/flutter_sound.git
path: flutter_sound
然后执行以下命令获取依赖:
flutter pub get
2.2 权限配置
在 OpenHarmony 项目中,需要配置麦克风权限才能进行录音。打开 ohos/entry/src/main/module.json5 文件,添加以下权限:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:reason_microphone",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
三、核心 API 详解
3.1 FlutterSoundPlayer 播放器
FlutterSoundPlayer 是音频播放的核心类,提供完整的播放控制功能。
class FlutterSoundPlayer {
PlayerState get playerState;
bool get isPlaying;
bool get isPaused;
bool get isStopped;
Stream<PlaybackDisposition>? get onProgress;
StreamSink<Food>? get foodSink;
Future<FlutterSoundPlayer?> openPlayer();
Future<void> closePlayer();
Future<Duration> startPlayer({
String? fromURI,
Codec codec = Codec.aacADTS,
TWhenFinished? whenFinished,
});
Future<void> stopPlayer();
Future<void> pausePlayer();
Future<void> resumePlayer();
Future<void> seekToPlayer(int milliSeconds);
Future<void> setVolume(double volume);
Future<void> setSpeed(double speed);
Future<void> setSubscriptionDuration(Duration duration);
}
3.2 FlutterSoundRecorder 录音器
FlutterSoundRecorder 是音频录制的核心类,提供完整的录制控制功能。
class FlutterSoundRecorder {
RecorderState get recorderState;
bool get isRecording;
bool get isPaused;
bool get isStopped;
Stream<RecordingDisposition>? get onProgress;
Future<FlutterSoundRecorder?> openRecorder();
Future<void> closeRecorder();
Future<void> startRecorder({
String? toFile,
Codec codec = Codec.aacADTS,
int sampleRate = 44100,
int numChannels = 2,
int bitRate = 128000,
AudioSource? audioSource,
});
Future<String> stopRecorder();
Future<void> pauseRecorder();
Future<void> resumeRecorder();
Future<bool> isEncoderSupported(Codec codec);
Future<void> setSubscriptionDuration(Duration duration);
}
3.3 PlayerState 播放状态
enum PlayerState {
isStopped,
isPlaying,
isPaused,
}
3.4 RecorderState 录制状态
enum RecorderState {
isStopped,
isPaused,
isRecording,
}
3.5 Codec 编码格式
enum Codec {
defaultCodec,
aacADTS,
opusOGG,
opusCAF,
mp3,
vorbisOGG,
pcm16,
pcm16WAV,
pcm16AIFF,
pcm16CAF,
flac,
aacMP4,
amrNB,
amrWB,
pcm8,
pcmFloat32,
pcmWebM,
opusWebM,
vorbisWebM,
}
3.6 AudioSource 音频源
enum AudioSource {
defaultSource,
microphone,
voiceDownlink,
camCorder,
remote_submix,
unprocessed,
voice_call,
voice_communication,
voice_performance,
voice_recognition,
voiceUpLink,
bluetoothHFP,
headsetMic,
lineIn,
}
四、播放器 API 详解
4.1 openPlayer 方法
打开播放器会话,必须在播放前调用。
Future<FlutterSoundPlayer?> openPlayer()
返回值:返回播放器实例。
使用示例:
FlutterSoundPlayer player = FlutterSoundPlayer();
await player.openPlayer();
4.2 closePlayer 方法
关闭播放器会话,释放资源。
Future<void> closePlayer()
使用示例:
await player.closePlayer();
player = null;
4.3 startPlayer 方法
开始播放音频。
Future<Duration> startPlayer({
String? fromURI,
Uint8List? fromDataBuffer,
Codec codec = Codec.aacADTS,
TWhenFinished? whenFinished,
int? numChannels,
int? sampleRate,
})
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
| fromURI | String? | 音频文件路径或 URL |
| fromDataBuffer | Uint8List? | 音频数据缓冲区 |
| codec | Codec | 音频编码格式,默认 aacADTS |
| whenFinished | TWhenFinished? | 播放完成回调 |
| numChannels | int? | 声道数(PCM 格式需要) |
| sampleRate | int? | 采样率(PCM 格式需要) |
返回值:返回音频总时长。
使用示例:
await player.startPlayer(
fromURI: 'https://example.com/audio.mp3',
codec: Codec.mp3,
whenFinished: () {
print('播放完成');
},
);
4.4 stopPlayer 方法
停止播放。
Future<void> stopPlayer()
使用示例:
await player.stopPlayer();
4.5 pausePlayer 方法
暂停播放。
Future<void> pausePlayer()
使用示例:
await player.pausePlayer();
4.6 resumePlayer 方法
恢复播放。
Future<void> resumePlayer()
使用示例:
await player.resumePlayer();
4.7 seekToPlayer 方法
跳转到指定位置。
Future<void> seekToPlayer(Duration position)
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
| position | Duration | 目标播放位置 |
使用示例:
await player.seekToPlayer(Duration(milliseconds: 30000));
await player.seekToPlayer(Duration(seconds: 30));
4.8 setVolume 方法
设置音量。
Future<void> setVolume(double volume)
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
| volume | double | 音量值,范围 0.0 ~ 1.0 |
使用示例:
await player.setVolume(0.5);
4.9 setSpeed 方法
设置播放速度。
Future<void> setSpeed(double speed)
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
| speed | double | 播放速度倍率 |
使用示例:
await player.setSpeed(1.5);
await player.setSpeed(0.5);
4.10 onProgress 属性
播放进度流,用于监听播放进度。
Stream<PlaybackDisposition>? get onProgress
PlaybackDisposition 结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| position | Duration | 当前播放位置 |
| duration | Duration | 音频总时长 |
使用示例:
player.onProgress!.listen((e) {
print('进度: ${e.position} / ${e.duration}');
});
五、录音器 API 详解
5.1 openRecorder 方法
打开录音器会话,必须在录音前调用。
Future<FlutterSoundRecorder?> openRecorder()
使用示例:
FlutterSoundRecorder recorder = FlutterSoundRecorder();
await recorder.openRecorder();
5.2 closeRecorder 方法
关闭录音器会话,释放资源。
Future<void> closeRecorder()
使用示例:
await recorder.closeRecorder();
recorder = null;
5.3 startRecorder 方法
开始录音。
Future<void> startRecorder({
String? toFile,
StreamSink<Food>? toStream,
Codec codec = Codec.aacADTS,
int sampleRate = 44100,
int numChannels = 2,
int bitRate = 128000,
AudioSource? audioSource,
})
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
| toFile | String? | 录音文件路径 |
| toStream | StreamSink? | 录音数据流 |
| codec | Codec | 音频编码格式 |
| sampleRate | int | 采样率,默认 44100 |
| numChannels | int | 声道数,默认 2 |
| bitRate | int | 比特率,默认 128000 |
| audioSource | AudioSource? | 音频源,默认 microphone |
使用示例:
await recorder.startRecorder(
toFile: 'recording.m4a',
codec: Codec.aacMP4,
sampleRate: 44100,
numChannels: 1,
bitRate: 128000,
audioSource: AudioSource.microphone,
);
5.4 stopRecorder 方法
停止录音。
Future<String> stopRecorder()
返回值:返回录音文件路径。
使用示例:
String path = await recorder.stopRecorder();
print('录音保存到: $path');
5.5 pauseRecorder 方法
暂停录音。
Future<void> pauseRecorder()
使用示例:
await recorder.pauseRecorder();
5.6 resumeRecorder 方法
恢复录音。
Future<void> resumeRecorder()
使用示例:
await recorder.resumeRecorder();
5.7 isEncoderSupported 方法
检查编码格式是否支持。
Future<bool> isEncoderSupported(Codec codec)
使用示例:
bool supported = await recorder.isEncoderSupported(Codec.mp3);
print('MP3 编码支持: $supported');
5.8 onProgress 属性
录音进度流,用于监听录音进度和分贝级别。
Stream<RecordingDisposition>? get onProgress
RecordingDisposition 结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| duration | Duration | 已录制时长 |
| dbPeakLevel | double | 分贝级别 |
使用示例:
recorder.onProgress!.listen((e) {
print('时长: ${e.duration}, 分贝: ${e.dbPeakLevel}');
});
六、OpenHarmony 平台实现原理
6.1 原生 API 映射
| Flutter API | OpenHarmony API |
|---|---|
| 播放器 | media.AVPlayer / audio.AudioRenderer |
| 录音器 | media.AVRecorder / audio.AudioCapturer |
| 文件播放 | AVPlayer.url = “fd://” + fd |
| 网络播放 | AVPlayer.url = “http://…” |
| 文件录制 | AVRecorder.prepare(config) |
6.2 播放器实现
OpenHarmony 使用 AVPlayer 播放普通音频格式,使用 AudioRenderer 播放 PCM 数据:
export class FlutterAVPlayer implements IPlayer {
private avPlayer: media.AVPlayer | null = null;
async startPlayer(codec: t_CODEC, path: string, dataBuffer: Uint8Array,
numChannels: number, sampleRate: number): Promise<boolean> {
this.avPlayer = await media.createAVPlayer();
this.setAVPlayerCallback();
if(path.startsWith("http")) {
this.avPlayer.url = path;
} else {
this.audioFile = fs.openSync(path, fs.OpenMode.READ_ONLY);
this.avPlayer.url = "fd://" + this.audioFile?.fd;
}
return true;
}
private setAVPlayerCallback(): void {
this.avPlayer?.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
switch (state) {
case "initialized":
this.avPlayer?.prepare();
break;
case "prepared":
await this.avPlayer?.play();
break;
case "completed":
this.callback?.audioPlayerDidFinishPlaying(true);
break;
case "playing":
this.callback?.startPlayerCompleted(true, this.avPlayer!.duration);
break;
}
});
}
}
6.3 录音器实现
OpenHarmony 使用 AVRecorder 录制音频,使用 AudioCapturer 录制 PCM 数据:
export class FlutterAVRecorder implements IRecorder {
private avRecorder: media.AVRecorder | null = null;
async startRecord(numChannels: number, sampleRate: number, bitRate: number,
codec: t_CODEC, path: string, audioSource: number): Promise<void> {
let audioCodec = media.CodecMimeType.AUDIO_AAC;
let fileFormat = media.ContainerFormatType.CFT_MPEG_4A;
if (codec === t_CODEC.mp3) {
audioCodec = media.CodecMimeType.AUDIO_MP3;
fileFormat = media.ContainerFormatType.CFT_MP3;
}
this.avProfile = {
audioBitrate: bitRate,
audioChannels: numChannels,
audioSampleRate: sampleRate,
audioCodec: audioCodec,
fileFormat: fileFormat
};
this.audioFile = fs.openSync(path, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
this.avConfig = {
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
profile: this.avProfile,
url: "fd://" + this.audioFile.fd.toString()
};
this.avRecorder = await media.createAVRecorder();
await this.avRecorder?.prepare(this.avConfig);
await this.avRecorder?.start();
}
}
6.4 编码格式映射
| Codec | OpenHarmony CodecMimeType | ContainerFormatType |
|---|---|---|
| aacADTS | AUDIO_AAC | CFT_MPEG_4A |
| aacMP4 | AUDIO_AAC | CFT_MPEG_4 |
| mp3 | AUDIO_MP3 | CFT_MP3 |
| amrNB | AUDIO_AMR_NB | CFT_AMR |
| amrWB | AUDIO_AMR_WB | CFT_AMR |
七、MethodChannel 通信协议
7.1 播放器 Channel 名称
const MethodChannel _channel = MethodChannel('com.dooboolab.flutter_sound_player');
7.2 录音器 Channel 名称
const MethodChannel _channel = MethodChannel('com.dooboolab.flutter_sound_recorder');
7.3 播放器方法列表
| 方法 | 参数 | 返回值 |
|---|---|---|
| openPlayer | - | int |
| closePlayer | - | void |
| startPlayer | path, codec, fromDataBuffer, … | Duration |
| stopPlayer | - | void |
| pausePlayer | - | void |
| resumePlayer | - | void |
| seekToPlayer | millis | void |
| setVolume | volume | void |
| setSpeed | speed | void |
| setSubscriptionDuration | duration | void |
7.4 录音器方法列表
| 方法 | 参数 | 返回值 |
|---|---|---|
| openRecorder | - | int |
| closeRecorder | - | void |
| startRecorder | path, codec, sampleRate, … | void |
| stopRecorder | - | String |
| pauseRecorder | - | void |
| resumeRecorder | - | void |
| isEncoderSupported | codec | bool |
| setSubscriptionDuration | duration | void |
八、实战案例
8.1 完整音频录制与播放示例

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(const MaterialApp(home: AudioRecorderPage()));
}
class AudioRecorderPage extends StatefulWidget {
const AudioRecorderPage({super.key});
State<StatefulWidget> createState() => _AudioRecorderPageState();
}
class _AudioRecorderPageState extends State<AudioRecorderPage> {
FlutterSoundPlayer? _player = FlutterSoundPlayer();
FlutterSoundRecorder? _recorder = FlutterSoundRecorder();
bool _isPlayerInited = false;
bool _isRecorderInited = false;
bool _isPlaybackReady = false;
String _recordPath = 'recording.m4a';
Codec _codec = Codec.aacMP4;
Duration _duration = Duration.zero;
Duration _position = Duration.zero;
double _volume = 1.0;
double _speed = 1.0;
StreamSubscription? _playerSubscription;
StreamSubscription? _recorderSubscription;
void initState() {
super.initState();
_init();
}
Future<void> _init() async {
await _player!.openPlayer();
_isPlayerInited = true;
await _recorder!.openRecorder();
_isRecorderInited = true;
bool supported = await _recorder!.isEncoderSupported(_codec);
if (!supported) {
_codec = Codec.aacADTS;
_recordPath = 'recording.aac';
}
await _recorder!.setSubscriptionDuration(const Duration(milliseconds: 100));
await _player!.setSubscriptionDuration(const Duration(milliseconds: 100));
_recorderSubscription = _recorder!.onProgress!.listen((e) {
setState(() {
_duration = e.duration;
});
});
_playerSubscription = _player!.onProgress!.listen((e) {
setState(() {
_position = e.position;
_duration = e.duration;
});
});
setState(() {});
}
void dispose() {
_playerSubscription?.cancel();
_recorderSubscription?.cancel();
_player!.closePlayer();
_player = null;
_recorder!.closeRecorder();
_recorder = null;
super.dispose();
}
Future<void> _startRecorder() async {
await _recorder!.startRecorder(
toFile: _recordPath,
codec: _codec,
sampleRate: 44100,
numChannels: 1,
bitRate: 128000,
);
setState(() {});
}
Future<void> _stopRecorder() async {
await _recorder!.stopRecorder();
_isPlaybackReady = true;
setState(() {});
}
Future<void> _startPlayer() async {
await _player!.startPlayer(
fromURI: _recordPath,
codec: _codec,
whenFinished: () {
setState(() {});
},
);
setState(() {});
}
Future<void> _stopPlayer() async {
await _player!.stopPlayer();
setState(() {});
}
Future<void> _pausePlayer() async {
await _player!.pausePlayer();
setState(() {});
}
Future<void> _resumePlayer() async {
await _player!.resumePlayer();
setState(() {});
}
String _formatDuration(Duration d) {
String twoDigits(int n) => n.toString().padLeft(2, '0');
String minutes = twoDigits(d.inMinutes.remainder(60));
String seconds = twoDigits(d.inSeconds.remainder(60));
return '$minutes:$seconds';
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('音频录制与播放')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildRecorderSection(),
const SizedBox(height: 24),
_buildPlayerSection(),
const SizedBox(height: 24),
_buildControlsSection(),
],
),
),
);
}
Widget _buildRecorderSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.mic, size: 24),
const SizedBox(width: 8),
const Text('录音器', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: _recorder!.isRecording ? Colors.red : Colors.grey,
borderRadius: BorderRadius.circular(12),
),
child: Text(
_recorder!.isRecording ? '录音中' : '已停止',
style: const TextStyle(color: Colors.white),
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _isRecorderInited && _recorder!.isStopped
? _startRecorder
: null,
icon: const Icon(Icons.fiber_manual_record),
label: const Text('开始录音'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
onPressed: _recorder!.isRecording ? _stopRecorder : null,
icon: const Icon(Icons.stop),
label: const Text('停止录音'),
),
),
],
),
const SizedBox(height: 12),
Text('时长: ${_formatDuration(_duration)}'),
],
),
),
);
}
Widget _buildPlayerSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.play_circle, size: 24),
const SizedBox(width: 8),
const Text('播放器', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: _player!.isPlaying ? Colors.green : Colors.grey,
borderRadius: BorderRadius.circular(12),
),
child: Text(
_player!.isPlaying
? '播放中'
: _player!.isPaused
? '已暂停'
: '已停止',
style: const TextStyle(color: Colors.white),
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _isPlaybackReady && _player!.isStopped
? _startPlayer
: null,
icon: const Icon(Icons.play_arrow),
label: const Text('播放'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
onPressed: _player!.isPlaying ? _pausePlayer : null,
icon: const Icon(Icons.pause),
label: const Text('暂停'),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
onPressed: _player!.isPaused ? _resumePlayer : null,
icon: const Icon(Icons.play_arrow),
label: const Text('恢复'),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
onPressed: !_player!.isStopped ? _stopPlayer : null,
icon: const Icon(Icons.stop),
label: const Text('停止'),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Text(_formatDuration(_position)),
Expanded(
child: Slider(
value: _duration.inMilliseconds > 0
? _position.inMilliseconds.toDouble()
: 0,
max: _duration.inMilliseconds.toDouble(),
onChanged: (value) async {
await _player!.seekToPlayer(Duration(milliseconds: value.toInt()));
},
),
),
Text(_formatDuration(_duration)),
],
),
],
),
),
);
}
Widget _buildControlsSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('播放控制', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
Row(
children: [
const Icon(Icons.volume_up, size: 20),
const SizedBox(width: 8),
const Text('音量:'),
Expanded(
child: Slider(
value: _volume,
min: 0.0,
max: 1.0,
onChanged: (value) async {
await _player!.setVolume(value);
setState(() {
_volume = value;
});
},
),
),
Text('${(_volume * 100).toStringAsFixed(0)}%'),
],
),
const SizedBox(height: 8),
Row(
children: [
const Icon(Icons.speed, size: 20),
const SizedBox(width: 8),
const Text('速度:'),
Expanded(
child: Slider(
value: _speed,
min: 0.25,
max: 2.0,
divisions: 7,
onChanged: (value) async {
await _player!.setSpeed(value);
setState(() {
_speed = value;
});
},
),
),
Text('${_speed}x'),
],
),
],
),
),
);
}
}
代码要点说明:
| 功能模块 | 实现方式 |
|---|---|
| 播放器初始化 | openPlayer() 打开会话 |
| 录音器初始化 | openRecorder() 打开会话 |
| 权限请求 | Permission.microphone.request() |
| 开始录音 | startRecorder(toFile: ...) |
| 停止录音 | stopRecorder() |
| 开始播放 | startPlayer(fromURI: ...) |
| 暂停播放 | pausePlayer() |
| 恢复播放 | resumePlayer() |
| 停止播放 | stopPlayer() |
| 进度监听 | onProgress!.listen() |
| 音量控制 | setVolume(double) |
| 速度控制 | setSpeed(double) |
| 资源释放 | closePlayer() / closeRecorder() |
九、常见问题与解决方案
9.1 麦克风权限问题
问题:录音时提示权限被拒绝。
解决方案:
确保在 module.json5 中正确配置权限,并在录音前请求权限:
var status = await Permission.microphone.request();
if (status != PermissionStatus.granted) {
throw Exception('麦克风权限未授权');
}
9.2 编码格式不支持
问题:某些编码格式在特定设备上不支持。
解决方案:
使用 isEncoderSupported 检查编码支持情况:
bool supported = await recorder.isEncoderSupported(Codec.mp3);
if (!supported) {
_codec = Codec.aacMP4;
}
9.3 播放进度不准确
问题:播放进度更新不及时或不准确。
解决方案:
设置订阅间隔以获得更频繁的更新:
await player.setSubscriptionDuration(const Duration(milliseconds: 100));
9.4 录音文件无法播放
问题:录制的文件无法播放或播放异常。
解决方案:
- 确保文件扩展名与编码格式匹配
- 检查采样率和声道数配置是否正确
- 确保录音完成后再进行播放
const List<String> ext = [
'', '.aac', '.opus', '.caf', '.mp3', '.ogg', '.pcm', '.wav',
'.aiff', '.caf', '.flac', '.mp4', '.amr', '.amr', '.pcm', '.pcm',
];
9.5 多个播放器冲突
问题:同时播放多个音频时出现问题。
解决方案:
每个播放器需要独立的实例:
FlutterSoundPlayer player1 = FlutterSoundPlayer();
FlutterSoundPlayer player2 = FlutterSoundPlayer();
await player1.openPlayer();
await player2.openPlayer();
十、与其他库对比
| 特性 | flutter_sound | audioplayers | just_audio |
|---|---|---|---|
| 音频播放 | ✅ | ✅ | ✅ |
| 音频录制 | ✅ | ❌ | ❌ |
| 多种编码格式 | ✅ | 有限 | 有限 |
| 流式播放 | ✅ | ❌ | ✅ |
| 流式录制 | ✅ | ❌ | ❌ |
| 播放速度控制 | ✅ | 有限 | ✅ |
| 音量控制 | ✅ | ✅ | ✅ |
| 进度监听 | ✅ | ✅ | ✅ |
| OpenHarmony 支持 | ✅ | ❌ | ❌ |
| 维护状态 | 活跃开发 | 活跃开发 | 活跃开发 |
建议:
- 需要录音功能:使用
flutter_sound - 只需要播放功能:可考虑
just_audio或audioplayers - 需要流式处理:使用
flutter_sound
十一、总结
flutter_sound 库为 Flutter for OpenHarmony 提供了完整的音频录制和播放能力,支持多种编码格式和丰富的控制选项,适合各种音频处理场景。
核心要点:
- 使用
FlutterSoundPlayer进行音频播放 - 使用
FlutterSoundRecorder进行音频录制 - 播放器和录音器都需要先打开会话再使用
- 使用完毕后必须关闭会话释放资源
- 录音前需要请求麦克风权限
- 使用
onProgress监听播放/录制进度 - 使用
isEncoderSupported检查编码格式支持
最佳实践:
- 在
initState中初始化播放器和录音器 - 在
dispose中关闭播放器和录音器 - 录音前检查并请求麦克风权限
- 使用
whenFinished回调处理播放完成事件 - 设置合适的订阅间隔以获得流畅的进度更新
- 根据平台支持情况选择合适的编码格式
更多推荐



所有评论(0)