【入门到精通】鸿蒙next应用开发:语音录制和声音动效实现案例

 往期鸿蒙5.0全套实战文章必看:(文中附带鸿蒙5.0全栈学习资料)


语音录制和声音动效实现

介绍

本示例使用AVrecord录制音频和AVrecord的getAudioCapturerMaxAmplitude接口获取振幅实现UI动效;使用AVplayer播放音频

效果图预览

使用说明

  1. 按住按钮开始录音。
  2. 释放发送语音消息。
  3. 拖拽到“文”字进行语音转文字模拟。
  4. 拖拽到“取消”则放弃发送。
  5. 录制完成后点击语音消息框可播放录音。

实现思路

  1. 利用组合手势来实现音频录制与取消录制。
//底部按钮行
Row() {
   Image($r("app.media.voice_record_dynamic_effect_icon"))
      .width($r("app.integer.voice_record_dynamic_effect_width_image"))
      .height($r("app.integer.voice_record_dynamic_effect_height_image"))
   Button($r("app.string.voice_record_dynamic_effect_button"))
      .type(ButtonType.Normal)
      .borderRadius($r("app.integer.voice_record_border_radio"))
      .backgroundColor(Color.White)
      .width($r("app.integer.voice_record_dynamic_effect_width_button"))
      .height($r("app.integer.voice_record_dynamic_effect_height_button"))
      .fontColor(Color.Black)
      .gesture(
         GestureGroup(GestureMode.Sequence,
            LongPressGesture()
               .onAction(() => {
                  this.mode = VerifyModeEnum.VOICE;
                  // 获取时间戳
                  this.timeStart = Math.floor(new Date().getTime() / Const.ANIMATION_DURATION);
                  this.flagInfoOpacity = Const.OPACITY_FALSE;
                  this.isListening = !this.isListening;
                  this.flagUpOpacity = Const.OPACITY_TRUE;
                  this.AVRecord.startRecordingProcess();
                  // 每隔100ms获取一次振幅
                  this.count = setInterval(() => {
                     if (this.AVRecord.maxAmplitude > Const.MIN_AMPLITUDE) {
                        this.maxNumber = (this.AVRecord.maxAmplitude) / Const.MAX_AMPLITUDE * Const.COLUMN_HEIGHT;
                        this.maxNumber = this.maxNumber >= 60 ? 60 : this.maxNumber;
                        this.minNumber = this.maxNumber - Const.HEIGHT_MIN;
                     } else {
                        this.maxNumber = Const.OPACITY_FALSE;
                        this.minNumber = Const.OPACITY_FALSE;
                     }
                     if (this.isListening) {
                        animateTo({ duration: Const.ANIMATION_DURATION, curve: Curve.EaseInOut }, () => {
                           this.yMax = Math.round(Math.random() * 60);
                           this.yMin = Math.round(Math.random() * 20);
                        })
                     }
                  }, Const.SET_INTERVAL_TIME);
               })
               .onActionEnd(() => {
                  clearInterval(this.count);
                  this.flagInfoOpacity = Const.OPACITY_TRUE;
                  this.yMax = Const.OPACITY_FALSE;
                  this.yMin = Const.OPACITY_FALSE;
                  this.AVRecord.stopRecordingProcess();
               }),
            // 上划左边取消,右边转文字
            PanGesture({ direction: PanDirection.Left | PanDirection.Right | PanDirection.Up, distance: 50 })
               .onActionStart((event: GestureEvent) => {
                  const offsetX = event.offsetX;
                  const offsetY = event.offsetY;
                  // 0=语音录制,1=转文字,2=取消
                  this.mode = getMode(offsetX, offsetY);
                  this.updateStateByMode();
               })
               .onActionUpdate((event: GestureEvent) => {
                  const offsetX = event.offsetX;
                  const offsetY = event.offsetY;
                  // 0=语音录制,1=转文字,2=取消
                  this.mode = getMode(offsetX, offsetY);
                  this.updateStateByMode();
               })
               .onActionEnd(() => {
                  this.doGestureEndBusiness();
               })
         )
            .onCancel(() => {
               if(this.timeStart === Const.OPACITY_FALSE){
                  promptAction.showToast({
                     message: $r("app.string.voice_record_verify_too_short"),
                     duration: 1000
                  })
                  return;
               }
               this.doGestureEndBusiness();
            })
      )
   Image($r("app.media.voice_record_dynamic_effect_emoji"))
      .width($r("app.integer.voice_record_dynamic_effect_width_image"))
      .height($r("app.integer.voice_record_dynamic_effect_height_image"))
   Image($r("app.media.voice_record_dynamic_effect_add"))
      .width($r("app.integer.voice_record_dynamic_effect_width_image"))
      .height($r("app.integer.voice_record_dynamic_effect_height_image"))
}
.justifyContent(FlexAlign.SpaceAround)
   .width($r('app.string.voice_record_dynamic_effect_width_full'))
   .height($r("app.integer.voice_record_dynamic_effect_height_row"))
   .backgroundColor($r("app.color.voice_record_dynamic_effect_color_row"))

  1. 在音频录制的时候通过getAudioCapturerMaxAmplitude获取声音振幅使UI变化。 (1)在录制音频的时候通过getAudioCapturerMaxAmplitude获取振幅; (2)对振幅做占比运算,随机取最大值与最小值之间的值(Math.floor(Math.random())来使column的高度随机变化;
// 开始录制对应的流程
async startRecordingProcess(): Promise<void> {
   if (this.avRecorder !== undefined) {
      await this.avRecorder.release();
      this.avRecorder = undefined;
   }
   // 1.创建录制实例
   this.avRecorder = await media.createAVRecorder();
   this.setAudioRecorderCallback();
   // 2.获取录制文件fd赋予avConfig里的url;参考FilePicker文档
   const context = getContext(this);
   const path = context.filesDir;
   const filepath = path + '/01.mp3';
   
   const file = fs.openSync(filepath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
   const fdNumber = file.fd;
   this.avConfig.url = 'fd://' + fdNumber;
   // 3.配置录制参数完成准备工作
   await this.avRecorder.prepare(this.avConfig);
   // 4.开始录制
   await this.avRecorder.start();
   
   // TODO:知识点:通过getAudioCapturerMaxAmplitude接口获取声音振幅大小。
   this.time = setInterval(() => {
      this.avRecorder!.getAudioCapturerMaxAmplitude((_: BusinessError, amplitude: number) => {
         this.maxAmplitude = amplitude;
      });
   }, Const.COLUMN_HEIGHT);
}


  1. 使用AVplayer播放已录制的音频。
//语音消息
Row() {
   Image($r("app.media.voice_record_dynamic_effect_volume"))
      .width($r("app.integer.voice_record_dynamic_effect_width_image"))
      .height($r("app.integer.voice_record_dynamic_effect_height_image"))
   Text(message.content)
      .fontSize($r("app.integer.voice_record_dynamic_effect_font_size_text"))
      .margin($r("app.integer.voice_record_message_margin"))
}
.borderRadius(Const.COLUMN_WIDTH)
   .justifyContent(FlexAlign.End)
   .width(Number(message.content) * Const.COLUMN_WIDTH + Const.HEIGHT_MIN)
   .backgroundColor($r("app.color.voice_record_dynamic_effect_color_message"))
   .margin({ top: $r('app.integer.voice_record_dynamic_effect_margin_top') })
   .padding({
      left: $r("app.integer.voice_record_dynamic_effect_margin"),
      right: $r("app.integer.voice_record_dynamic_effect_margin")
   })
   .onClick(() => {
      this.AVPlayer.avPlayerUrlDemo();
   })

工程结构&模块类型

normalcaptu                                      // har类型
|---src
|   |---main
|   |     |---ets
|   |     |  |---common                        
|   |        |    |---CommonConstants.ets         // 常量定义 
|   |     |  |---components                        
|   |        |    |---ButtonWithWaterRipples.ets  // 音频振幅效果组件 
|   |     |  |---enums                        
|   |        |    |---MessageType.ets             // 消息类型枚举 
|   |        |    |---VerifyModeEnum.ets          // 识别类型枚举 
|   |     |  |---models                        
|   |        |    |---AudioRecorder.ets           // 音频录制 
|   |        |    |---AVPlayer.ets                // 音频播放 
|   |     |  |---pages                          
|   |        |    |---Index.ets                   // 主页面
|   |     |  |---utils                        
|   |        |    |---RecordUtil.ets              // 工具方法类 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值