vue 语音功能

归根结底 语音功能就是录音,上传,播放,知道了这个就可以开始啦

1.录音

1)可以使用navigator.mediaDevices.getUserMedia
startRecording() {
      navigator.mediaDevices.getUserMedia({ audio: true })
        .then((stream) => {
          this.audioRecorder = new MediaRecorder(stream);

          this.audioRecorder.ondataavailable = (e) => {
            if (e.data.size > 0) {
              this.audioRecorder.onstop = () => {
                const audioBlob = new Blob([e.data], { type: 'audio/wav' });
                var file = new File([audioBlob], '语音', {
                type: 'application/json',
                lastModified: Date.now()
            });
                const audioUrl = URL.createObjectURL(audioBlob);
                console.log(audioUrl)
                this.messages.push({ id: Date.now(), audioUrl });
              };
              // this.audioRecorder.stop();
            }
          };

          this.audioRecorder.start();
        })
        .catch((error) => {
          console.error('Error accessing microphone:', error);
        });
    },
2)可以使用插件npm i js-audio-recorder

插件的使用可以看另一篇文章

vue使用js-audio-recorder实现录音功能-CSDN博客vue使用js-audio-recorder实现录音功能https://blog.csdn.net/anwenagululu/article/details/133906760

2.上传

const formData = new FormData();
      const blob = this.recorder.getWAVBlob(); // 获取wav格式音频数据
      // 此处获取到blob对象后需要设置fileName满足当前项目上传需求,其它项目可直接传把blob作为file塞入formData
      const newbolb = new Blob([blob], { type: "audio/wav" });
      const fileOfBlob = new File([newbolb], new Date().getTime() + ".wav");
      formData.append("file", fileOfBlob);
      const url = window.URL.createObjectURL(fileOfBlob);
      this.src = url;
      uni.showLoading({
        title: "上传中",
      });

      console.log(11111111111111);
      uni.uploadFile({
        url: `${indexConfig.baseUrl}/common/uploadFile`,
        filePath: this.src,
        name: "file",
        formData: {},
        header: {
          Authorization: uni.getStorageSync("accessToken"),
        },
        success: (res) => {
          uni.hideLoading();

          if (res && res.data) {
            const req = JSON.parse(res.data);
            if (req.code == 200) {
              let img = indexConfig.assetsPath + req.data;
              this.$emit("ok", 4, img, this.durationSeconds);
              this.cancelRecord();
              this.isShowAudio = true;
            }
          }
        },
        fail: () => {
          uni.hideLoading();
          uni.$u.toast("发送失败,请重新发送");
          this.cancelRecord();
          this.isShowAudio = true;
        },
      });

3.播放

1)用audio

<audio controls :src="message.audioUrl"></audio>

2)用uni的api   uni.createInnerAudioContext()

uni.createInnerAudioContext() | uni-app官网uni-app,uniCloud,serverlessicon-default.png?t=N7T8https://uniapp.dcloud.net.cn/api/media/audio-context.html#createinneraudiocontext

const innerAudioContext = uni.createInnerAudioContext();
innerAudioContext.play();
innerAudioContext.pause();
innerAudioContext.onEnded(() => {
  console.log("音频播放结束");
});

4.完整代码

组件

<template>
  <div>
    <u-modal
      :showConfirmButton="false"
      style="width: 100%; height: 100%"
      :show="show"
      :title-style="{ color: 'red' }"
    >
      <!-- 音频录制 -->
      <div class="audio-record" v-show="recordStatus">
        <!-- <img
        class="cancel-img"
        width="20"
        src="@/assets/image/close.png"
        @click="cancelRecord()"
      /> -->
        <u-icon
          class="cancel-img"
          @click="cancelRecord()"
          name="close"
          color="rgb(184 187 193)"
          size="26"
        ></u-icon>
        <p class="duration-seconds-style">
          <span>已录制&nbsp;{{ durationSeconds }}″</span>
          <!-- <span v-show="countDownRecord">{{ countDownSecond }}″&nbsp;后停止录制</span> -->
        </p>

        <div class="icon">
          <div class="voice-animation" ref="voiceAnimation">
            <p v-for="item in 7" :key="item"></p>
          </div>
        </div>

        <div class="record-status" v-show="!startRecord" @click="startAudioRecord">
          <p>开始录制</p>
        </div>
        <div class="btn" v-show="startRecord">
          <div class="cancel" @click="cancelRecord">
            <p>取消</p>
          </div>
          <div class="send" @click="sendRecord">
            <p>发送</p>
          </div>
        </div>
      </div>

      <!-- 语音条 -->
      <!-- <div class="audio-detail-msg" v-show="isShowAudio">
      <div
        class="audio-style"
        :class="{ 'add-animation': isPlay }"
        :style="{ width: handleAudioStyleWidth() }"
        @click="playAudio()"
      >
        <div class="small"></div>
        <div class="middle"></div>
        <div class="large"></div>
      </div>
      <div class="duration-seconds">{{ showDurationSeconds }}″</div>
    </div> -->
    </u-modal>
  </div>
</template>

<script>
import Recorder from "js-audio-recorder";
import indexConfig from "@/config/index.config";
export default {
  props: {
    show: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      // show: false,
      recorder: null,
      showDurationSeconds: 0, // 语音时长
      durationSeconds: 0, // 语音时长
      isPlay: false, // 当前语音是否正在播放
      playAudioTimer: null, // 语音播放定时器
      recordStatus: true, // 音频录制显示状态
      isDurationSeconds: false, // 开始录制时间状态
      countDownRecord: false, // 倒计时录制状态
      countDownSecond: 10, // 倒计时录制时间
      startRecord: false, // 开始录制按钮状态
      isShowAudio: false, // 语音条是否显示
    };
  },
  created() {
    this.recorder = new Recorder();
  },
  watch: {
    durationSeconds(newVal) {
      if (newVal === 2) {
        this.isDurationSeconds = false;
        clearInterval(this.confTime);

        this.countDownRecord = true;
        this.confTime = setInterval(() => {
          this.countDownSecond--;
          this.durationSeconds++;
        }, 1000);
      }
    },

    // countDownSecond(newVal) {
    //   if (newVal === 0) this.sendRecord();
    // },
  },
  methods: {
    // 取消语音录制
    cancelRecord() {
      this.showDurationSeconds = this.durationSeconds;
      // this.recordStatus = false;
      this.startRecord = false;
      this.isDurationSeconds = false;
      this.countDownRecord = false;
      this.$refs.voiceAnimation.classList.remove("start");
      clearInterval(this.confTime);
      this.confTime = null;
      this.durationSeconds = 0;
      this.countDownSecond = 10;
      this.$emit("cancel");
      // this.show = false;
    },

    // 开始录制语音消息
    startAudioRecord() {
      this.startRecord = true;
      this.isDurationSeconds = true;
      this.$refs.voiceAnimation.classList.add("start");
      this.recorder = new Recorder();
      Recorder.getPermission().then(
        () => {
          console.log("开始录音");
          this.recorder.start(); // 开始录音
        },
        (error) => {
          this.$message({
            message: "请先允许该网页使用麦克风",
            type: "info",
          });
          console.log(`${error.name} : ${error.message}`);
        }
      );
      this.confTime = setInterval(() => {
        this.durationSeconds++;
      }, 1000);
    },

    // 完成录制并发送
    sendRecord() {
      if (!this.durationSeconds) {
        this.cancelRecord();
        uni.$u.toast("录制时间太短!");
        return;
      }
      this.recorder.pause(); // 暂停录音
      console.log("上传录音"); // 上传录音

      const formData = new FormData();
      const blob = this.recorder.getWAVBlob(); // 获取wav格式音频数据
      // 此处获取到blob对象后需要设置fileName满足当前项目上传需求,其它项目可直接传把blob作为file塞入formData
      const newbolb = new Blob([blob], { type: "audio/wav" });
      const fileOfBlob = new File([newbolb], new Date().getTime() + ".wav");
      formData.append("file", fileOfBlob);
      const url = window.URL.createObjectURL(fileOfBlob);
      this.src = url;
      console.log(newbolb, fileOfBlob, this.src, "fileOfBlob");
      uni.showLoading({
        title: "上传中",
      });

      console.log(11111111111111);
      uni.uploadFile({
        url: `${indexConfig.baseUrl}/common/uploadFile`,
        filePath: this.src,
        name: "file",
        formData: {},
        header: {
          Authorization: uni.getStorageSync("accessToken"),
        },
        success: (res) => {
          uni.hideLoading();

          if (res && res.data) {
            const req = JSON.parse(res.data);
            if (req.code == 200) {
              let img = indexConfig.assetsPath + req.data;
              console.log(222222222, this.durationSeconds);
              this.$emit("ok", 4, img, this.durationSeconds);
              this.cancelRecord();
              this.isShowAudio = true;
            }
          }
        },
        fail: () => {
          uni.hideLoading();
          this.show = false;
          uni.$u.toast("发送失败,请重新发送");
          this.cancelRecord();
          this.isShowAudio = true;
        },
      });
    },

    // 设置语音条宽度样式
    handleAudioStyleWidth() {
      if (this.showDurationSeconds === 1) {
        return "38px";
      } else if (this.showDurationSeconds > 1 && this.showDurationSeconds < 20) {
        return `${38 + (this.showDurationSeconds / 10) * 36}px`;
      } else if (this.showDurationSeconds >= 20) {
        return `${106.39 + (this.showDurationSeconds / 10) * 18.935}px`;
      }
    },

    // 播放语音
    playAudio() {
      // 若当前为播放状态则关闭
      if (this.isPlay) return (this.isPlay = false);

      this.isPlay = true;
      // 计时停止播放语音
      this.playAudioTimer = setTimeout(() => {
        this.isPlay = false;
      }, parseInt(this.showDurationSeconds * 1000));
    },
  },
};
</script>
<style lang="scss">
p {
  margin: 0;
}
// 音频录制
.audio-record {
  // position: absolute;
  // top: 100%;
  // left: 50%;
  // transform: translate(-50%, -50%);
  width: 150px;
  height: 145px;
  background: rgba(0, 0, 0, 0.8);
  padding: 20px;
  // border-radius: 15px;
  color: #ccc;
  font-size: 15px;
  text-align: center;

  .cancel-img {
    position: absolute;
    top: 10px;
    right: 10px;
    cursor: pointer;
  }

  .duration-seconds-style {
    height: 20px;
  }

  .icon {
    display: flex;
    justify-content: center;
    align-items: flex-end;
    margin: 18px 0 30px;
  }

  .voice-animation {
    margin-left: 15px;

    p {
      height: 3px;
      margin-top: 4px;
      background: #666;
    }

    p:nth-of-type(1) {
      width: 28px;
    }

    p:nth-of-type(2) {
      width: 24px;
    }

    p:nth-of-type(3) {
      width: 20px;
    }

    p:nth-of-type(4) {
      width: 16px;
    }

    p:nth-of-type(5) {
      width: 12px;
    }

    p:nth-of-type(6) {
      width: 8px;
    }

    p:nth-of-type(7) {
      width: 5px;
    }

    &.start {
      p:nth-of-type(1) {
        animation: backgroundInfinite7 1.5s ease-in-out infinite;
      }

      p:nth-of-type(2) {
        animation: backgroundInfinite6 1.5s ease-in-out infinite;
      }

      p:nth-of-type(3) {
        animation: backgroundInfinite5 1.5s ease-in-out infinite;
      }

      p:nth-of-type(4) {
        animation: backgroundInfinite4 1.5s ease-in-out infinite;
      }

      p:nth-of-type(5) {
        animation: backgroundInfinite3 1.5s ease-in-out infinite;
      }

      p:nth-of-type(6) {
        animation: backgroundInfinite2 1.5s ease-in-out infinite;
      }

      p:nth-of-type(7) {
        background: #f5f5f5;
      }
    }
  }

  .record-status {
    width: 100px;
    height: 28px;
    margin: 0 auto;
    line-height: 28px;
    color: #ffffff;
    border-radius: 2px;

    &:hover {
      color: #04113d;
      background: #e1e1e1;
    }

    p {
      cursor: pointer;
    }
  }

  .btn {
    display: flex;
    justify-content: space-between;
    align-items: center;

    div {
      width: 60px;
      height: 30px;
      color: #888;
      cursor: pointer;
      line-height: 30px;
      border-radius: 4px;
    }

    .cancel:hover {
      color: #cbcbcb;
      // background: #830808;
    }

    .send:hover {
      color: #cbcbcb;
      // background: #4a8b2a;
    }
  }
}

// 语音条
.audio-detail-msg {
  display: flex;
  align-items: center;

  .audio-style {
    display: flex;
    align-items: center;
    height: 32px;
    margin-right: 8px;
    padding: 0 10px;
    border-radius: 4px;
    background: #f5f5f5;

    .small {
      border: 4px solid #4c4c4c;
      border-top-color: transparent;
      border-left-color: transparent;
      border-bottom-color: transparent;
    }

    .middle {
      width: 16px;
      height: 16px;
      margin-left: -11px;
      opacity: 1;
    }

    .large {
      width: 24px;
      height: 24px;
      margin-left: -19px;
      opacity: 1;
    }

    & > div {
      border: 2px solid #4c4c4c;
      border-top-color: transparent;
      border-left-color: transparent;
      border-bottom-color: transparent;
      border-radius: 50%;
      box-sizing: border-box;
    }

    &.add-animation {
      .middle {
        animation: show2 1.2s ease-in-out infinite;
      }
      .large {
        animation: show3 1.2s ease-in-out infinite;
      }
    }
  }

  // 语音播放动画
  @keyframes show2 {
    0% {
      opacity: 0;
    }
    10% {
      opacity: 1;
    }
    100% {
      opacity: 0;
    }
  }

  @keyframes show3 {
    0% {
      opacity: 0;
    }
    50% {
      opacity: 1;
    }
    60% {
      opacity: 0;
    }
    100% {
      opacity: 0;
    }
  }

  // 语音录制动画
  @keyframes backgroundInfinite2 {
    0% {
      background: #666;
    }
    20% {
      background: #f5f5f5;
    }
    95% {
      background: #f5f5f5;
    }
    100% {
      background: #666;
    }
  }

  @keyframes backgroundInfinite3 {
    0% {
      background: #666;
    }
    30% {
      background: #f5f5f5;
    }
    85% {
      background: #f5f5f5;
    }
    100% {
      background: #666;
    }
  }

  @keyframes backgroundInfinite4 {
    0% {
      background: #666;
    }
    55% {
      background: #f5f5f5;
    }
    75% {
      background: #f5f5f5;
    }
    100% {
      background: #666;
    }
  }

  @keyframes backgroundInfinite5 {
    0% {
      background: #666;
    }
    45% {
      background: #666;
    }
    60% {
      background: #f5f5f5;
    }
    75% {
      background: #f5f5f5;
    }
    100% {
      background: #666;
    }
  }

  @keyframes backgroundInfinite6 {
    0% {
      background: #666;
    }
    65% {
      background: #666;
    }
    85% {
      background: #f5f5f5;
    }
    100% {
      background: #666;
    }
  }

  @keyframes backgroundInfinite7 {
    0% {
      background: #666;
    }
    75% {
      background: #666;
    }
    95% {
      background: #f5f5f5;
    }
    100% {
      background: #666;
    }
  }
}
/deep/.u-modal__content {
  padding: 0 !important;
}
/deep/.u-modal {
  width: 100% !important;
}
/deep/.u-line {
  border: 0 !important;
}
</style>

5.效果展示

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值