• Flutter开发笔记 —— 语音消息功能实现


    前言

    最近在开发一款即时通讯(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 voicePlayList =[]; //用来控制语音播放列表

    • String voicePath = “”; //临时储存语音文件路径

    初始化声音配置方法

    
      /*
       * @author Marinda
       * @date 2023/6/26 15:25
       * @description 初始化声音设置
       */
      initSoundSetting() async{
        await flutterSoundPlayer.openPlayer();
        await recordSound.openRecorder();
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    录制用户语音

    
      /*
       * @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();
        });
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    播放实现

    
      /*
       * @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);
          }
        );
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    结束录制

    
      /*
       * @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 = "";
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    结束语

    难度不大,主要是围绕着录音文件进行处理

    值得注意的点是在播放录音文件时
    记得获取目标的二进制流一并携带至startPlayer方法fromDataBuffer字段中
    否则可能会出现无法播放或程序未响应等危险情况!

    感谢你的观看!

  • 相关阅读:
    Feign 如何设置超时时间
    [npm]npm包的分类
    基于YOLOv8模型的烟火目标检测系统(PyTorch+Pyside6+YOLOv8模型)
    Java复习第二弹!
    zynq soc 计划
    3. 常用服务器工具安装
    mybatis详解(全)
    前端-(1)
    【论文阅读】社交网络传播最大化问题-03
    [附源码]java毕业设计智慧教室预约
  • 原文地址:https://blog.csdn.net/qq_33638188/article/details/133648571