1***@qq.com
1***@qq.com
  • 发布:2021-03-06 17:38
  • 更新:2021-03-10 17:08
  • 阅读:918

【报Bug】ios播放录音文件闪退

分类:uni-app

产品分类: uniapp/App

PC开发环境操作系统: Mac

PC开发环境操作系统版本号: Macbook pro 11.2.1

HBuilderX类型: 正式

HBuilderX版本号: 3.1.4

手机系统: iOS

手机系统版本号: IOS 14

手机厂商: 苹果

手机机型: iPhone mini

页面类型: vue

打包方式: 云端

项目创建方式: HBuilderX

示例代码:
<template>  
  <view :class="{ 'hide': !showRecord }" class="record">  
    <view v-if="state === 'start'" class="duration">当前录音时长{{ duration }}s</view>  
    <!-- #ifndef H5 -->  
    <view v-if="state === 'stop'&&url" class="play-record">  
      <view v-if="playState" class="play" @tap.stop="pause">  
        <u-icon name="pause" />  
      </view>  
      <view v-if="!playState" class="play" style="padding-left:6rpx" @tap.stop="play">  
        <u-icon name="play-right-fill" />  
      </view>  
      <text>{{ openduration }}</text>  
      <view class="scheduling">  
        <slider v-model="value" block-size="18" background-color="#fff" active-color="#66B1FF" @changing="sliderChanging" @change="sliderChange" />  
      </view>  
      <text>{{ duration }}</text>  
    </view>  
    <!-- #endif -->  
    <!-- #ifdef H5 -->  
    <view v-if="state === 'stop' && localId" class="play-record-h5">  
      <view v-if="playState" class="play-h5" @tap.stop="pause">  
        <u-icon name="pause" />  
      </view>  
      <view v-if="!playState" class="play-h5" style="padding-left:6rpx" @tap.stop="play">  
        <u-icon name="play-right-fill" />  
      </view>  
    </view>  
    <!-- #endif -->  
    <view class="reception">  
      <view class="reception-line">  
        <view class="reception-line1" />  
        <view class="reception-line2" />  
      </view>  
      <view class="reception-icon" @tap.stop="accomplish">  
        <u-icon v-if="state==='stop' && (url || localId)" name="checkmark" />  
        <u-icon v-else name="mic" />  
      </view>  
    </view>  
    <view class="operation-view">  
      <view v-if="state === 'start'" class="stop" @tap.stop="stop" />  
      <view v-if="state === 'stop'" class="afresh" @tap.stop="openRecord">  
        <u-icon name="reload" />  
      </view>  
    </view>  
    <view class="cancel" @tap.stop="cancel">  
      <u-icon name="trash" />  
    </view>  
  </view>  
</template>  
<script>  
// #ifdef H5  
import { isWeiXin } from '@/utils/index.js'  
import jWeixin from 'jweixin-module'  
// #endif  
export default {  
  name: 'FrimRecord',  
  data() {  
    return {  
      duration: '00.00', // 录音时长  
      openduration: '00.00', // 播放时间  
      state: 'stop', // 当前在录音还是停止录音  
      playState: false, // 是否正在播放  
      showRecord: false, // 显示隐藏组件  
      url: '', // 获取到的本地音频  
      recorderManager: null, // 录音  
      innerAudioContext: null, // 播放音频  
      timer: null, // 定时器获取录音时长  
      value: 0, // 播放进度  
      localId: null // 微信录音的  
    }  
  },  
  methods: {  
    // h5open() {  
    //   // 先试用一次看是否能够录音  
    //   jWeixin.startRecord({  
    //     success: () => {  
    //       setTimeout(() => {  
    //         // 如果能录音则去录音界面  
    //         jWeixin.stopRecord({  
    //           success: (res) => {  
    //             setTimeout(() => {  
    //               this.openRecord()  
    //             }, 300)  
    //           }  
    //         })  
    //       }, 300)  
    //     },  
    //     fail: (res) => {  
    //       uni.showToast({  
    //         title: '录音出现错误!',  
    //         icon: 'none',  
    //         position: 'bottom'  
    //       })  
    //     }  
    //   })  
    // },  
    // 开始录音  
    openRecord() {  
      // 显示遮盖层  
      uni.showLoading({  
        title: '开始启动中',  
        mask: true,  
        success: () => {  
          // 初始化录音时长  
          this.duration = '0.00'  
          // 初始化值  
          this.url = ''  
          // 初始化播放时长  
          this.value = 0  
          this.openduration = '00.00'  
          // 把录音装态修改为停止录音  
          this.state = 'stop'  
          // #ifndef H5  
          // 显示组件  
          this.showRecord = true  
          // 有个问题,结束播放是异步执行,如果没在开始录音前结束播放操作,就会闪退,除非把播放销毁  
          if (this.innerAudioContext) {  
            this.innerAudioContext.stop()  
            this.innerAudioContext.destroy()  
            this.innerAudioContext = null  
            this.playState = false  
          }  
          // 判断录音是否存在  
          if (!this.recorderManager) {  
            // 创建录音  
            this.recorderManager = uni.getRecorderManager()  
            // 录音开始  
            this.recorderManager.onStart(res => {  
              // 关闭遮罩  
              uni.hideLoading()  
              // 修改状态为开始录音  
              this.state = 'start'  
              // 清空定时器  
              clearInterval(this.timer)  
              // 设置定时器录音时长  
              let i = 0  
              this.timer = setInterval(() => {  
                i += 1  
                // 判断时间,如果录音超过五十秒者停止提示用户停止录音  
                if (i >= 5000) {  
                  uni.showToast({  
                    title: '已达到最大录音时长!',  
                    icon: 'none',  
                    position: 'bottom'  
                  })  
                  this.stop()  
                }  
                this.duration = (i / 100).toFixed(2)  
              }, 10)  
            })  
            // 录音出现错误  
            this.recorderManager.onError(res => {  
              // 清空定时器  
              clearInterval(this.timer)  
              // 关闭遮罩  
              uni.hideLoading()  
              // 初始化录音时长  
              this.duration = '0.00'  
              // 初始化值  
              this.url = ''  
              // 把录音装态修改为停止录音  
              this.state = 'stop'  
              // 回调信息  
              this.finish({  
                type: 'error',  
                manager: res  
              })  
              // 提示信息给用户  
              uni.showToast({  
                title: '录音出现错误!',  
                icon: 'none',  
                position: 'bottom'  
              })  
            })  
            // 录音结束  
            this.recorderManager.onStop(res => {  
              // 清空定时器  
              clearInterval(this.timer)  
              // 设置值  
              this.url = res.tempFilePath  
              // 把录音装态修改为停止录音  
              this.state = 'stop'  
            })  
          }  
          // 开始录音  
          this.recorderManager.start({ format: 'mp3' })  
          // #endif  
          // #ifdef H5  
          // 如果是微信浏览器开始录音否则进行提示并关闭页面  
          if (isWeiXin()) {  
            // 显示组件  
            this.showRecord = true  
            // 暂停播放  
            this.pause()  
            this.localId = ''  
            // 开始录音  
            jWeixin.startRecord({  
              success: () => {  
                // 关闭遮罩  
                uni.hideLoading()  
                // 把录音装态修改为开始录音  
                this.state = 'start'  
                // 清空定时器  
                clearInterval(this.timer)  
                let i = 0  
                this.timer = setInterval(() => {  
                  // 判断时间,如果录音超过五十秒者停止提示用户停止录音  
                  i += 1  
                  if (i >= 5000) {  
                    this.stop()  
                    uni.showToast({  
                      title: '已达到最大录音时长!',  
                      icon: 'none',  
                      position: 'bottom'  
                    })  
                  }  
                  this.duration = (i / 100).toFixed(2)  
                }, 10)  
              },  
              fail: () => {  
                // 关闭遮罩  
                uni.hideLoading()  
                // 清空定时器  
                clearInterval(this.timer)  
                // 初始化录音时长  
                this.duration = '0.00'  
                // 初始化录音值  
                this.url = ''  
                this.localId = ''  
                // 把录音装态修改为停止录音  
                this.state = 'stop'  
                // 提示用户  
                uni.showToast({  
                  title: '录音出现错误!',  
                  icon: 'none',  
                  position: 'bottom'  
                })  
                // 回调信息  
                this.finish({  
                  type: 'error',  
                  manager: '录音出现错误'  
                })  
              }  
            })  
          } else {  
            uni.hideLoading()  
            uni.showToast({  
              title: '请在微信浏览器中进行录音操作!',  
              icon: 'none',  
              position: 'bottom'  
            })  
            this.finish({  
              type: 'error',  
              manager: '录音出现错误'  
            })  
          }  
          // #endif  
        }  
      })  
    },  
    // 结束录音  
    finish(obj) {  
      this.$emit('finish', obj)  
    },  
    // 滑块改变  
    sliderChange(e) {  
      // 获取当当前滑块的值  
      this.value = e.detail.value  
      // 根据百分比获取到应该播放的时间  
      const time = (e.detail.value / 100 * Number(this.duration)).toFixed(2)  
      // 设置播放时间  
      this.openduration = time  
      // 如果播放存在则设置播放音频  
      if (this.innerAudioContext) {  
        this.innerAudioContext.src = this.url  
        // 如果是暂停播放则设置开始播放时间并开始播放否则开始播放  
        if (this.innerAudioContext.paused) {  
          this.innerAudioContext.seek(time)  
          this.innerAudioContext.play()  
        } else {  
          this.innerAudioContext.seek(time)  
        }  
      } else {  
        // 如果没有则跳转播放  
        this.play(time)  
      }  
    },  
    // 滑块改变中  
    sliderChanging(e) {  
      // 根据百分比获取到应该播放的时间  
      const time = (e.detail.value / 100 * Number(this.duration)).toFixed(2)  
      this.openduration = time  
    },  
    // 点击完成  
    accomplish() {  
      if (!(this.state === 'stop' && (this.url || this.localId))) {  
        return  
      }  
      // 暂停播放  
      this.pause()  
      // #ifdef H5  
      if (isWeiXin()) {  
        // 上传到服务器  
        jWeixin.uploadVoice({  
          localId: this.localId, // 需要上传的音频的本地ID,由stopRecord接口获得  
          isShowProgressTips: 1, // 默认为1,显示进度提示  
          success: (res) => {  
            // 返回服务器地址回调信息数据  
            this.finish({  
              type: 'success',  
              url: res.serverId  
            })  
            // 关闭组件  
            this.showRecord = false  
          },  
          fail: () => {  
            uni.showToast({  
              title: '微信上传服务器错误!',  
              icon: 'none',  
              position: 'bottom'  
            })  
            this.finish({  
              type: 'error',  
              manager: '微信上传出现错误'  
            })  
          }  
        })  
      }  
      // #endif  
      // #ifndef H5  
      // 回调信息数据  
      this.finish({  
        type: 'success',  
        url: this.url  
      })  
      // 关闭组件  
      this.showRecord = false  
      // #endif  
    },  
    // 取消录音  
    cancel() {  
      clearInterval(this.timer)  
      // #ifndef H5  
      // 如果在录音则停止录音  
      if (this.state === 'start') {  
        this.recorderManager.stop()  
      }  
      // 如果在播放则停止播放  
      if (this.innerAudioContext) {  
        this.innerAudioContext.destroy()  
        this.innerAudioContext = null  
      }  
      // #endif  
      // #ifdef H5  
      if (isWeiXin()) {  
        // 如果在录音则停止录音  
        if (this.state === 'start') {  
          uni.showLoading({  
            title: '停止录音...',  
            mask: true  
          })  
          jWeixin.stopRecord({  
            success: (res) => {  
              this.state = 'stop'  
              uni.hideLoading()  
            },  
            fail: () => {  
              uni.hideLoading()  
            }  
          })  
        }  
        // 如果在播放则停止播放  
        if (this.localId) {  
          jWeixin.pauseVoice({  
            localId: this.localId  
          })  
          this.playState = false  
        }  
      }  
      // #endif  
      this.finish({ type: 'cancel' })  
      // 关闭组件  
      this.showRecord = false  
    },  
    // 停止录音  
    stop() {  
      clearInterval(this.timer)  
      // #ifndef H5  
      this.recorderManager.stop()  
      // #endif  
      // #ifdef H5  
      if (isWeiXin()) {  
        uni.showLoading({  
          title: '停止录音...',  
          mask: true  
        })  
        jWeixin.stopRecord({  
          success: (res) => {  
            this.localId = res.localId  
            this.state = 'stop'  
            uni.hideLoading()  
          },  
          fail: () => {  
            uni.hideLoading()  
          }  
        })  
      }  
      // #endif  
    },  
    // 暂停播放  
    pause() {  
      // #ifndef H5  
      if (this.innerAudioContext) {  
        this.innerAudioContext.pause()  
      }  
      // #endif  
      // #ifdef H5  
      if (isWeiXin()) {  
        if (this.localId) {  
          jWeixin.pauseVoice({  
            localId: this.localId  
          })  
          this.playState = false  
        }  
      }  
      // #endif  
    },  
    wxplay(obj) {  
      // 其它页面调用播放  
      if (isWeiXin()) {  
        // 如果存在  
        if (this.localId) {  
          uni.showLoading({  
            title: '开始播放...',  
            mask: true,  
            success: () => {  
              // 如果在播放  
              if (!obj.type) {  
                // 开始播放  
                jWeixin.playVoice({  
                  localId: this.localId  
                })  
                obj.success(true)  
                uni.hideLoading()  
                // 播放完成  
                jWeixin.onVoicePlayEnd({  
                  success: (res) => {  
                    obj.success(false)  
                    uni.hideLoading()  
                  }  
                })  
              } else {  
                // 暂停播放  
                jWeixin.pauseVoice({  
                  localId: this.localId  
                })  
                obj.success(false)  
                uni.hideLoading()  
              }  
            }  
          })  
        }  
      }  
    },  
    // 播放音频  
    play(value) {  
        console.log(encodeURI(this.url))  
        //return  
      // #ifndef H5  
      if (!this.innerAudioContext) {  
        this.innerAudioContext = uni.createInnerAudioContext()  
        this.innerAudioContext.autoplay = true  
        this.innerAudioContext.src = encodeURI(this.url)  
        // 开始播放  
        this.innerAudioContext.onPlay(res => {  
          this.playState = true  
        })  
        // 暂停播放  
        this.innerAudioContext.onPause(res => {  
          this.playState = false  
        })  
        // 播放发生错误  
        this.innerAudioContext.onError(res => {  
          console.log(res)  
          this.playState = false  
        })  
        // 音频播放结束  
        this.innerAudioContext.onEnded(res => {  
          this.playState = false  
          this.openduration = this.duration  
          this.value = 100  
        })  
        // 音频进入可以播放状态,但不保证后面可以流畅播放  
        this.innerAudioContext.onCanplay(res => {  
          this.duration = this.innerAudioContext.duration.toFixed(2)  
        })  
        // 播放进行状态  
        this.innerAudioContext.onTimeUpdate(res => {  
          if (this.innerAudioContext) {  
            this.openduration = this.innerAudioContext.currentTime.toFixed(2)  
            this.value = (Number(this.openduration) / Number(this.duration) * 100).toFixed(0)  
          }  
        })  
      } else {  
        // 判断是否是暂停播放  
        if (this.innerAudioContext._options.src !== this.url) {  
          this.innerAudioContext.src = this.url  
        }  
      }  
      // 设置播放开始时长  
      if (value) {  
        this.innerAudioContext.startTime = value  
      }  
      // 开始播放  
      this.innerAudioContext.play()  
      // #endif  
      // #ifdef H5  
      if (isWeiXin()) {  
        jWeixin.playVoice({  
          localId: this.localId  
        })  
        this.playState = true  
        jWeixin.onVoicePlayEnd({  
          success: (res) => {  
            this.playState = false  
          }  
        })  
      }  
      // #endif  
    }  
  }  
}  
</script>  
<style lang="scss" scoped>  
.record{  
  position: fixed;  
  width: 100%;  
  height: 100%;  
  z-index: 10;  
  top: 0;  
  background-color: rgba(0,0,0,0.8);  
  &.hide{  
    top: 100%;  
  }  
}  
// 录音时长  
.duration{  
  position: absolute;  
  color: #fff;  
  font-size: 32rpx;  
  text-align: center;  
  top: 30%;  
  width: 100%;  
}  
// #ifdef H5  
.play-record-h5{  
  position: absolute;  
  top: 20%;  
  left: 50%;  
  transform: translateX(-50%);  
  font-size: 60rpx;  
  width: 120rpx;  
  height: 120rpx;  
  border-radius: 100%;  
  background: #fff;  
  text-align: center;  
  line-height: 120rpx;  
  color: $uni-color-primary;  
}  
// #endif  
// #ifndef H5  
.play-record{  
  position: absolute;  
  top: 40%;  
  width: 100%;  
  display: flex;  
  align-items: center;  
  justify-content: center;  
  color: #fff;  
  .scheduling{  
    width: 50%;  
    display: flex;  
    align-items: center;  
    margin: 0 20rpx;  
    slider{  
      width: 100%;  
    }  
  }  
  text{  
    font-size: 28rpx;  
  }  
  .play{  
    width: 50rpx;  
    height: 50rpx;  
    border-radius: 100%;  
    background: #fff;  
    margin-right: 10rpx;  
    font-size: 30rpx;  
    color: $uni-color-primary;  
    text-align: center;  
    line-height: 50rpx;  
  }  
}  
// #endif  
// 操作按钮  
.operation-view,.cancel{  
  position: absolute;  
  left: 80rpx;  
  bottom: 20%;  
  width: 100rpx;  
  height: 100rpx;  
  border-radius: 100%;  
  background: #fff;  
  display: flex;  
  align-items: center;  
  justify-content: center;  
  &>.stop{  
    width: 36rpx;  
    height: 36rpx;  
    border-radius: 8rpx;  
    background: red;  
  }  
  .afresh{  
    color: $uni-color-primary;  
    font-size: 40rpx;  
  }  
}  
.cancel{  
  left: auto;  
  right: 80rpx;  
  color: $u-type-error;  
  background-color: $u-type-error-light;  
  font-size: 40rpx;  
  border: 2rpx solid $u-type-error-disabled;  
  box-sizing: border-box;  
}  
// 波浪  
.reception{  
  position: absolute;  
  bottom: 18%;  
  width: 200rpx;  
  height: 200rpx;  
  left: calc(50% - 100rpx);  
}  
.reception-line1, .reception-line::after,.reception-line::before, .reception-line2{  
  position: absolute;  
  left: 0;  
  top: 0;  
  width: 100%;  
  height: 100%;  
  border: 1rpx solid #fff;  
  border-radius: 30%;  
  display: inline-block;  
  transform: rotate(300deg);  
}  
.reception-line {  
  position: relative;  
  display: inline-block;  
  width: 200rpx;  
  height: 200rpx;  
  animation: roateOne 4s linear infinite;  
  &::after{  
    content: '';  
    border-radius: 40%;  
    transform: rotate(120deg);  
  }  
  &::before {  
    content: '';  
    border-radius: 45%;  
    transform: rotate(240deg);  
  }  
}  
.reception-line1{  
  border-radius: 35%;  
  transform: rotate(60deg);  
}  
.reception-icon{  
  position: absolute;  
  width: 100%;  
  height: 100%;  
  border-radius: 50%;  
  top: 0;  
  left: 0;  
  color: $uni-color-primary;  
  align-content: center;  
  justify-content: center;  
  display: flex;  
  font-size: 60rpx;  
  z-index: 1;  
  &::before{  
    content: '';  
    position: absolute;  
    left: 50%;  
    top: 50%;  
    width: 80%;  
    height: 80%;  
    border-radius: 50%;  
    display: inline-block;  
    background: #fff;  
    z-index: -1;  
    animation: roateOne2 4s linear infinite;  
    transform-origin: 0 0;  
    transform: scale(0.8) translate(-50%, -50%);  
  }  
}  
@keyframes roateOne2 {  
  0% {  
    transform: scale(0.88) translate(-50%, -50%);  
  }  
  50% {  
    transform: scale(1) translate(-50%, -50%);  
  }  
  100% {  
    transform: scale(0.88) translate(-50%, -50%);  
  }  
}  
@keyframes roateOne {  
  from {  
    transform: rotate(0);  
  }  
  to {  
    transform: rotate(360deg);  
  }  
}  
</style>

操作步骤:

ios录音后点击播放

预期结果:

ios正常播放

实际结果:

手机卡死,过一会儿闪退

bug描述:

录音文件路径为
_doc/uniapp_temp_1615022730209/recorder/Recorder_001.mp3

播放报错 {"errMsg":"MediaError","errCode":-5}
手机卡,死过一会儿闪退

2021-03-06 17:38 负责人:无 分享
已邀请:
1***@qq.com

1***@qq.com (作者) - 一个菜逼开发者

没有人遇到吗?

1***@qq.com

1***@qq.com (作者) - 一个菜逼开发者

顶顶顶顶

  • DCloud_UNI_Anne

    请提供一个简单可稳定复现的完整demo(上传附件)

    2021-03-10 17:09

  • 1***@qq.com (作者)

    回复 DCloud_UNI_Anne: 找到原因了,安卓的@tap.stop="play" 点击就没有给play传参,ios的传了even事件,导致判断错误,设置了播放时间,设置的播放时间不是数字,导致闪退

    2021-03-16 10:27

DCloud_UNI_Anne

DCloud_UNI_Anne

请提供一个简单可稳定复现的完整demo(上传附件)

该问题目前已经被锁定, 无法添加新回复