<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>
- 发布:2021-03-06 17:38
- 更新:2021-03-10 17:08
- 阅读:918
产品分类: uniapp/App
PC开发环境操作系统: Mac
PC开发环境操作系统版本号: Macbook pro 11.2.1
HBuilderX类型: 正式
HBuilderX版本号: 3.1.4
手机系统: iOS
手机系统版本号: IOS 14
手机厂商: 苹果
手机机型: iPhone mini
页面类型: vue
打包方式: 云端
项目创建方式: HBuilderX
示例代码:
操作步骤:
ios录音后点击播放
ios录音后点击播放
预期结果:
ios正常播放
ios正常播放
实际结果:
手机卡死,过一会儿闪退
手机卡死,过一会儿闪退
bug描述:
录音文件路径为
_doc/uniapp_temp_1615022730209/recorder/Recorder_001.mp3
播放报错 {"errMsg":"MediaError","errCode":-5}
手机卡,死过一会儿闪退
3 个回复
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
请提供一个简单可稳定复现的完整demo(上传附件)