鸿蒙开发融云demo录制语音消息

鸿蒙开发融云demo录制语音消息

融云鸿蒙版是不带UI的,得自己一步步搭建。录制语音,长按录制效果是最难弄的。还有录制声音的声音大小波动。

这次来讲如何长按录制语音消息

一、思路:

自定义TouchEvent,然后调用融云的录制语音方法。声音大小用AudioRecorder.getInstance().avRecorder?.getAudioCapturerMaxAmplitude

二、效果图:
三、关键代码:
typescript 复制代码
// 播放语音的播放器
  // 区分语音还是文字输入状态
  @State isVoiceInput:boolean = false
  microphonePermissions: Permissions = 'ohos.permission.MICROPHONE';
  hasMicrophonePermissions:boolean = false
  // 是否正在请求麦克风权限
  isRequestMicrophonePermissions:boolean = false
  // Column高度最大值,用于长按是声音的振幅
  @State yMax: number = 0;
  // Column高度最小值
  @State yMin: number = 0;
  // 声音文件名称,不能重复,否则播放自己本地语音,只会播放最后一条
  voiceName:string = ''
  // 当前录音时间,用于录制到50s后倒计时
  currentRecordVoiceTime:number = 0
  @State countDownVoiceText:string = ''
  maxMicVolumeCountId: number = 0
  // 开始录制时的时间戳
  audioTapTs: number = 0;
  audioFile: fs.File | null = null;
  // 创建音频录制与播放实例
  avPlayer?:media.AVPlayer
  @State voicePlayState: AnimationStatus = AnimationStatus.Initial
  @State voicePlayMessageId:number = 0
  // 按住说话 录音模态
  @State
  showTalkContainer: boolean = false
  // 长按状态
  @State
  pressCancelVoicePostText: PressCancelVoicePostText = PressCancelVoicePostText.none
  // "x "的坐标
  xScreenOffset: ScreenOffset = {
    x: 0,
    y: 0,
    width: 0,
    height: 0
  }

  // 按住说话 持续触发
  onPressTalk = async (event: TouchEvent) => {
    if (event.type === TouchType.Down) {
      this.currentRecordVoiceTime = 0
      this.countDownVoiceText = ''
      PermissionUtil.checkPermissions(this.microphonePermissions).then((result:boolean) => {
        if (result) {
          // 有权限
          this.hasMicrophonePermissions = true
          this.isRequestMicrophonePermissions = false
          // 手指按下时触发
          this.pressCancelVoicePostText = PressCancelVoicePostText.presssing
          // 按下
          this.showTalkContainer = true
          //  开始录音
          this.startRecordVoice()
        } else {
          this.isRequestMicrophonePermissions = true
          // 问麦克风权限
          PermissionUtil.requestPermissionsEasy(this.microphonePermissions).then((result:boolean)=>{
            if (result) {
              // 有权限
              this.hasMicrophonePermissions = true
              // 这里先不录,因为用户放开手去点击允许权限了
              //this.startRecordVoice()
            } else {
              this.hasMicrophonePermissions = false
            }
          })
        }
      })
      

    } else if (event.type === TouchType.Move) {
      if (this.hasMicrophonePermissions && !this.isRequestMicrophonePermissions) {
        // 手指移动时持续触发
        this.pressCancelVoicePostText = PressCancelVoicePostText.presssing
        // 获取当前手指的坐标
        const x = event.touches[0].displayX
        const y = event.touches[0].displayY
        // 判断是否碰到了 "X"
        let isTouchX = this.xScreenOffset.x <= x && this.xScreenOffset.x + this.xScreenOffset.width >= x &&
          this.xScreenOffset.y <= y && this.xScreenOffset.y + this.xScreenOffset.width >= y

        if (isTouchX) {
          // 取消发送
          this.pressCancelVoicePostText = PressCancelVoicePostText.cancelVoice
        }
      }

    } else if (event.type === TouchType.Up) {
      // 松开手
      if (this.showTalkContainer) {
        this.showTalkContainer = false
        clearInterval(this.maxMicVolumeCountId)
        animateTo({ duration: 0 }, () => {
          this.yMax = 0
          this.yMin = 0
        })
        if (this.hasMicrophonePermissions && !this.isRequestMicrophonePermissions) {
          if (this.pressCancelVoicePostText === PressCancelVoicePostText.cancelVoice) {
            // 取消发送
            AudioRecorder.getInstance().stopRecordingProcess()
          } else {
            // 发送录音
            this.recordAudioEndAndSendMessage()
          }
        }
      }
    }
  }
typescript 复制代码
// 正在说话 页面布局
  @Builder
  TalkContainerBuilder() {
    Column() {
      //   1 中心的提示   显示波浪线
      Column() {
          // 声纹
        ButtonWithWaterRipples({ yMax: this.yMax, yMin: this.yMin })
        if (this.countDownVoiceText){
          Text(`${this.countDownVoiceText}"后将停止录音`)
            .fontColor($r('app.color.color_white'))
            .margin({
              top:10
            })
        }
      }
      .height(80)
      .width("50%")
      .backgroundColor(this.pressCancelVoicePostText === PressCancelVoicePostText.cancelVoice ? Color.Red : "#95EC6A")
      .position({
        top: "40%",
        left: "50%"
      })
      .translate({
        x: "-50%"
      })
      .borderRadius(10)
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)

      //   2 取消和转文字
      Column({space:30}) {

        // 3  松开发送
        Text(this.pressCancelVoicePostText === PressCancelVoicePostText.cancelVoice ? '取消发送' : "松开发送")
          .fontColor("#fff")
          .width("100%")
          .textAlign(TextAlign.Center)

          Text("X")
            .fontSize(20)
            .width(60)
            .height(60)
            .borderRadius(30)
            .fontColor(this.pressCancelVoicePostText === PressCancelVoicePostText.cancelVoice ? "#000" : "#ccc")
            .backgroundColor(this.pressCancelVoicePostText === PressCancelVoicePostText.cancelVoice ? "#fff" : "#333")
            .textAlign(TextAlign.Center)
            .align(Alignment.Center)
            .fontColor("#ccc")
            .id("aabb")
            .onAppear(() => {
              let modePosition: componentUtils.ComponentInfo = componentUtils.getRectangleById("aabb");
              this.xScreenOffset.x = px2vp(modePosition.screenOffset.x)
              this.xScreenOffset.y = px2vp(modePosition.screenOffset.y)
              this.xScreenOffset.width = px2vp(modePosition.size.width)
              this.xScreenOffset.height = px2vp(modePosition.size.height)
            })
      }
      .width("100%")
      .position({
        bottom: "23%"
      })
      .padding({
        left: 60, right: 60
      })

      //   4 底部白色大球
      Row() {

      }
      .width(600)
      .height(600)
      .backgroundColor("#fff")
      .position({
        bottom: 0,
        left: "50%"
      })
      .translate({
        x: "-50%",
        y: "70%"
      })
      .borderRadius("50%")

    }
    .width("100%")
    .height("100%")
    .backgroundColor("rgba(0,0,0,0.5)")
  }
四、鸿蒙融云Demo源码结构图:

有问题或者需要完整源码demo的私信我

相关推荐
动物园猫23 分钟前
Compose Multiplatform 三方库 Coil(coil-core)的 OpenHarmony 鸿蒙化适配实战
华为·harmonyos
5008430 分钟前
React Native for OpenHarmony 实战:三方库 react-native-device-uptime 的鸿蒙化适配指南
javascript·分布式·react native·react.js·harmonyos
星辰徐哥10 小时前
鸿蒙平台 KDevelop 集成开发环境适配实战:基于 Electron 壳方案的跨平台多语言代码编辑器开发
electron·编辑器·harmonyos
熊猫钓鱼>_>10 小时前
开源鸿蒙平台 KMP 三方库 Essenty 适配全流程:从生命周期抽象到状态重建验证
华为·开源·harmonyos·鸿蒙·kmp·atomgit·essenty
李游Leo12 小时前
HarmonyOS 7 文搜图实战 04:图片增删、索引重建与异常恢复完善 SnapSeek 可维护性【鸿蒙心迹】
harmonyos
三掌柜66616 小时前
# ArkWeb 手记 03|把 JSBridge 做成协议
harmonyos
轻口味16 小时前
HarmonyOS 7 新特性5:平行视界——轻视界文集的两栏阅读与窗口状态边界矩阵真机实测
华为·矩阵·harmonyos·鸿蒙·平行视界
动物园猫17 小时前
Compose Multiplatform 三方库 compose-icons(Octicons)的 OpenHarmony 鸿蒙化适配实战
华为·ar·harmonyos
旺仔Sec18 小时前
2026年江西省职业院校技能大赛鸿蒙应用开发赛项竞赛任务书(高职组)样题
华为·harmonyos
曲鸟18 小时前
体验完鸿蒙AI后的几点感受
人工智能·华为·harmonyos