h5实现录音功能

发布时间 2023-04-11 16:12:05作者: zaijinyang

封装组件(soundRecording.vue):

<template>
  <view class="recorder"> </view>
</template>

<script>
export default {
  data() {
    return {
      isUserMedia: false,
      stream: null,
      audio: null,
      recorder: null,
      chunks: []
    };
  },
  mounted() {
    /**
     * 	error 事件的返回状态
     * 	100: 请在HTTPS环境中使用
     * 	101: 浏览器不支持
     *  201: 用户拒绝授权
     *  500: 未知错误
     * */
    if (origin.indexOf('https') === -1) {
      this.$emit('error', '100');
      uni.showModal({
        title: '提示',
        content: '请在https环境中使用录音功能,确认返回上一个页面',
        showCancel: false,
        success: (res) => {
          if (res.confirm) {
            uni.navigateBack();
          }
        }
      });
    }
    if (!navigator.mediaDevices || !window.MediaRecorder) {
      this.$emit('error', '101');
      uni.showModal({
        title: '提示',
        content: '当前浏览器不支持录音功能,确认返回上一个页面',
        showCancel: false,
        success: (res) => {
          if (res.confirm) {
            uni.navigateBack();
          }
        }
      });
    }
    this.getRecorderManager();
  },
  methods: {
    getRecorderManager() {
      this.audio = document.createElement('audio');
      navigator.mediaDevices
        .getUserMedia({ audio: true })
        .then((stream) => {
          this.isUserMedia = true;
          stream.getTracks().forEach((track) => {
            track.stop();
          });
        })
        .catch((err) => {
          this.onErrorHandler(err);
        });
    },
    start() {
      if (!this.isUserMedia) {
        uni.showModal({
          title: '提示',
          content: '当前设备不支持,确认返回上一个页面',
          showCancel: false,
          success: (res) => {
            if (res.confirm) {
              uni.navigateBack();
            }
          }
        });
      }
      navigator.mediaDevices
        .getUserMedia({ audio: true })
        .then((stream) => {
          this.stream = stream;
          this.recorder = new MediaRecorder(stream);
          this.recorder.ondataavailable = this.getRecordingData;
          this.recorder.onstop = this.saveRecordingData;
          this.recorder.start();
        })
        .catch((err) => {
          this.onErrorHandler(err);
        });
    },
    stop() {
      if (this.recorder) {
        this.recorder.stop();
        this.stream.getTracks().forEach((track) => {
          track.stop();
        });
      }
    },
    getRecordingData(e) {
      this.chunks.push(e.data);
    },
    saveRecordingData() {
      const blob = new Blob(this.chunks, { type: 'audio/mpeg' }),
        localUrl = URL.createObjectURL(blob);
      this.chunks = [];
      let lock = true;
      const temporaryAudio = document.createElement('audio');
      temporaryAudio.src = localUrl;
      temporaryAudio.muted = true;
      temporaryAudio.load();
      temporaryAudio.play();
      temporaryAudio.addEventListener('timeupdate', (e) => {
        if (!Number.isFinite(temporaryAudio.duration)) {
          temporaryAudio.currentTime = Number.MAX_SAFE_INTEGER;
          temporaryAudio.currentTime = 0;
        } else {
          document.body.append(temporaryAudio);
          document.body.removeChild(temporaryAudio);
          if (lock) {
            lock = false;
            const recorder = {
              data: blob,
              duration: temporaryAudio.duration,
              localUrl: localUrl
            };
            this.$emit('success', recorder);
          }
        }
      });
    },
    onErrorHandler(err) {
      console.log(err);
      if (err.name === 'NotAllowedError') {
        this.$emit('error', '201');
        uni.showModal({
          title: '提示',
          content: '用户拒绝了当前浏览器的访问请求,确认返回上一个页面',
          showCancel: false,
          success: (res) => {
            if (res.confirm) {
              uni.navigateBack();
            }
          }
        });
      }

      if (err.name === 'NotReadableError') {
        this.$emit('error', '101');
        uni.showModal({
          title: '提示',
          content: '当前浏览器不支持,确认返回上一个页面',
          showCancel: false,
          success: (res) => {
            if (res.confirm) {
              uni.navigateBack();
            }
          }
        });
      }

      this.$emit('error', '500');
      uni.showModal({
        title: '提示',
        content: '调用失败,确认返回上一个页面',
        showCancel: false,
        success: (res) => {
          if (res.confirm) {
            uni.navigateBack();
          }
        }
      });
    }
  },
  destroyed() {
    this.stop();
  }
};
</script>

使用组件:

<template>
  <view>
    <view class="audio" v-if="recorder">
      <audio :src="recorder.localUrl" name="本地录音" controls="true"></audio>
      <br />
      <button type="primary" @click="handlerSave">保存录音</button>
    </view>
    <h3 v-else>点击下方按钮录音</h3>
    <div class="container" v-if="status">
      <div class="wave0"></div>
      <div class="wave1"></div>
    </div>
    <view @click="handlerOnCahnger" class="statusBox" :class="{ active: status }">
      <view class="status"></view>
    </view>
    <sound-recording ref="recorder" @success="handlerSuccess" @error="handlerError"></sound-recording>
  </view>
</template>

<script>
import SoundRecording from '@/components/soundRecording/soundRecording.vue';
export default {
  components: {
    SoundRecording
  },
  data() {
    return {
      status: false,
      recorder: null
    };
  },
  methods: {
    handlerSave() {
      uni.downloadFile({
        url: this.recorder.localUrl, //仅为示例,并非真实的资源
        success: (res) => {
          if (res.statusCode === 200) {
            var oA = document.createElement('a');
            oA.download = ''; // 设置下载的文件名,默认是'下载'
            oA.href = res.tempFilePath; //临时路径再保存到本地
            document.body.appendChild(oA);
            oA.click();
            oA.remove(); // 下载之后把创建的元素删除
          }
        },
        fail: (err) => {
          uni.showToast({
            title: `下载失败${err}`
          });
        }
      });
    },
    handlerOnCahnger() {
      if (this.status) {
        this.$refs.recorder.stop();
      } else {
        this.$refs.recorder.start();
      }
      this.status = !this.status;
    },
    handlerSuccess(res) {
      console.log(res);
      this.recorder = res;
    },
    handlerError(code) {
      switch (code) {
        case '101':
          uni.showModal({
            content: '当前浏览器版本较低,请更换浏览器使用,推荐在微信中打开。'
          });
          break;
        case '201':
          uni.showModal({
            content: '麦克风权限被拒绝,请刷新页面后授权麦克风权限。'
          });
          break;
        default:
          uni.showModal({
            content: '未知错误,请刷新页面重试'
          });
          break;
      }
    }
  }
};
</script>

<style lang="scss" scoped>
.audio {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 20rpx;
}

h3 {
  text-align: center;
  padding: 20rpx;
}

.statusBox {
  z-index: 11;
  width: 120rpx;
  height: 120rpx;
  background-color: aliceblue;
  border-radius: 50%;
  box-shadow: 5rpx 5rpx 10rpx rgba(000, 000, 000, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  position: fixed;
  left: 50%;
  bottom: 120rpx;
  transform: translateX(-50%);

  .status {
    width: 60rpx;
    height: 60rpx;
    background-color: aliceblue;
    border-radius: 50%;
    border: 10rpx solid #5c5c66;
  }

  &.active {
    background-color: rgba(118, 218, 255, 0.45);

    .status {
      background-color: rgba(118, 218, 255, 0.45);
      border: 10rpx solid rgba(255, 255, 255, 0.75);
    }
  }
}

.container {
  z-index: 10;
  position: fixed;
  bottom: -200rpx;
  padding: 0;
  border: 0;
  width: 750rpx;
  height: 750rpx;
  background-color: rgb(118, 218, 255);
}

.wave0,
.wave1,
.wave2 {
  position: absolute;
  width: 750rpx * 2;
  height: 750rpx * 2;
  margin-top: -150%;
  margin-left: -50%;
  background-color: rgba(255, 255, 255, 0.4);
  border-radius: 45%;
  animation: spin 15s linear -0s infinite;
  z-index: 1;
  /*            border: 1px solid;*/
}

.wave1 {
  margin-top: -152%;
  background-color: rgba(255, 255, 255, 0.9);
  border-radius: 47%;
  animation: spin 30s linear -15s infinite;
  z-index: 2;
}

@keyframes spin {
  0% {
    transform: translate(-0%, -0%) rotate(0deg) scale(1);
  }

  25% {
    transform: translate(-1%, -1%) rotate(90deg) scale(1);
  }

  50% {
    transform: translate(-0%, -2%) rotate(180deg) scale(1);
  }

  75% {
    transform: translate(1%, -1%) rotate(270deg) scale(1);
  }

  100% {
    transform: translate(-0%, -0%) rotate(360deg) scale(1);
  }
}
</style>

效果:

 

 注意:需要调用录音功能的域名必须是https,否则无法调用!