前言
最近在开发一款即时通讯(IM)的聊天App,在实现语音消息功能模块后,写下该文章以做记录。
注:本文不提供相关图片资源以及IM聊天中具体实现代码,单论语音功能实现思路
需求分析
比起上来直接贴代码,我们先来逐步分析一下一个正常语音消息的需求是如何的?
- 长按语音按钮录制用户语音内容
- 松开按钮后发送语音消息至目标
从上可得,我们需要针对于用户的语音 录制 & 播放 方面下手!
Flutter_sound
目标地址:https://pub.dev/packages/flutter_sound
简介:Flutter_sound 是一款可以处理用户声音库
通过该插件的GitHub示例中可以了解到实现录制语音和播放的相关API为
- FlutterSoundPlayer下的startRecorder方法 (录制)
- FlutterSoundPlayer下的startPlayer方法 (播放)
实现思路
初始化Flutter_Sound配置
定义相关变量
- FlutterSoundPlayer flutterSoundPlayer = FlutterSoundPlayer(); //声音播放器
- FlutterSoundRecorder recordSound = FlutterSoundRecorder(); //声音录制器
- Timer? recordTimer // 计时器,用来控制录音时长;
- String timeString = “” // 用来做回显时长
- List<String> voicePlayList =[]; //用来控制语音播放列表
- String voicePath = “”; //临时储存语音文件路径
初始化声音配置方法
/*
* @author Marinda
* @date 2023/6/26 15:25
* @description 初始化声音设置
*/
initSoundSetting() async{
await flutterSoundPlayer.openPlayer();
await recordSound.openRecorder();
}
录制用户语音
/*
* @author Marinda
* @date 2023/6/26 15:31
* @description 录音
*/
recordSound() async{
PermissionStatus status = await Permission.microphone.request();
int time = 0;
//权限校验
if (status != PermissionStatus.granted) throw RecordingPermissionException("麦克风权限未授权!");
var dir = await getExternalStorageDirectory();
Uuid uuid = Uuid();
String filePath = p.join(dir?.path ?? "",uuid.v4()+".mp4");
File file = File(filePath);
file.openWrite();
state.voicePath.value = filePath;
Log.i("录音保存的位置:${filePath}");
await state.recordSound.startRecorder(
//目标文件位置
toFile: filePath,
//这里可以认为是那种源
codec: Codec.aacMP4,
//采样率
bitRate: 8000,
//为1即可
numChannels: 1
);
recordTimer = Timer.periodic(Duration(seconds: 1), (_) {
time++;
timeString = time.toString();
});
}
播放实现
/*
* @author Marinda
* @date 2023/10/7 14:28
* @description 播放语音信息 目前先做本地语音缓存处理
*/
playVoice(String voiceUrl) async{
Uint8List uint8list = Uint8List(0);
//视为网络http
if(voiceUrl.startsWith("http")){
uint8list = ...获取MP4文件二进;
}else{
File voiceFile = File(voiceUrl);
//如果不存在
if(!voiceFile.existsSync()){
BotToast.showText(text: "语音播放失败");
return;
}
uint8list = await voiceFile.readAsBytes();
}
//如果存在则进行移除播放
if(voicePlayList.contains(tag)){
voicePlayList.remove(tag);
await flutterSoundPlayer.stopPlayer();
return;
}
//加入语音信息队列
voicePlayList.add(tag);
await flutterSoundPlayer.startPlayer(
fromURI: data.expandAddress,
fromDataBuffer: uint8list,
codec: Codec.aacMP4,
sampleRate: 8000,
numChannels: 1,
whenFinished: (){
// 播放完毕
state.voicePlayList.remove(tag);
}
);
}
结束录制
/*
* @author Marinda
* @date 2023/6/26 15:33
* @description
*/
stopRecordSound() async{
await recordSound.stopRecorder();
if(recordTimer!.isActive){
recordTimer!.cancel();
recordTimer = null;
}
// 这里实现你的语音消息发送逻辑
Log.i("停止录制!");
timeString.value = "";
voicePath.value = "";
}
结束语
难度不大,主要是围绕着录音文件进行处理
值得注意的点是在播放录音文件时,记得获取目标的二进制流一并携带至startPlayer方法fromDataBuffer字段中,否则可能会出现无法播放或程序未响应等危险情况。
感谢你的观看!