uni-app 微信小程序在iphone手机上交替播放(暂停)音频和录音(暂停)功能时,导致部分录音丢失
uni-app 微信小程序在iphone手机上交替播放(暂停)音频和录音(暂停)功能时,导致部分录音丢失
| 产品分类 | uni小程序SDK |
|---|---|
| 手机系统 | iOS |
| 手机系统版本号 | IOS 14 |
| 手机厂商 | 苹果 |
| 手机机型 | iphone11, iphone12(估计iphone系列) |
| 页面类型 | vue |
| SDK版本号 | 2.19.0 |
示例代码:
主功能代码
<template>
<view class="lp-record">
<!-- 多选控件 -->
<view class="conbox record">
<view class="panel source-player" v-if="source">
<view class="head">原音播放</view>
<audio-player ref="audioPlayer" :audio="{src:source}"></audio-player>
</view>
<view class="panel">
<!-- 此处可放置倒计时,可根据需要自行添加 -->
<view class="time">
{{showRecordTime}}
</view>
<view class="c999">
最短{{minTime}}秒,最长{{maxTime}}秒
</view>
<view class="record-box" @touchmove.stop.prevent="onMoveHandler">
<!-- 空占位 -->
<view class="btn empty" v-if="isRecording" style="background: unset;"></view>
<view class="btn recording" @touchstart="onTouchStartHandler"
@touchend="onTouchEndHandler">
<text class="ws-icon" :class="{flash:isRecording&& !isRecordPaused}" style="font-size: 35rpx;">
{{isRecording && !isRecordPaused ? '暂停' : '录音'}}
</text>
</view>
<view class="btn confirm" @touchstart.stop="onRecordEndHandler" v-if="isRecording"></view>
<view class="btn confirm" @touchstart.stop="onConfirmHandler" v-if="!isRecording && playPath"></view>
</view>
<view class="c666 fz32 domess">
{{isRecording ? (isRecordPaused ? '已暂停' : '录音中') : (playPath ? '录音完成' : '点击开始录音')}}
</view>
<view v-if="playPath">
<view class="head">录音播放</view>
<audio-player :audio="{src:playPath}"></audio-player>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
frame: 50, // 执行绘画的频率,单位毫秒
recordTime: 0, //录音时长
isRecording: false, //是否录制中
isRecordPaused: false, //是否录制暂停
playing: 0, //是否播放中
playPath: "",
recorderManager: null, //录音
innerAudioContext: null, //播放
source: 'https://statici.jpworld.cn/files/aaaaaa0802.mp3',
maxTime: 600,
minTime: 5,
canPuase: true,
}
},
computed: {
showRecordTime() {
var strs = "";
var m = Math.floor(this.recordTime / 60);
if (m < 10) strs = "0" + m;
var s = this.recordTime % 60;
strs += (s < 10) ? ":0" + s : ":" + s;
return strs
},
},
onLoad() {
console.log('onload');
var _this = this;
this.initValue();
//获取录音权限
try {
uni.authorize({
scope: 'scope.record',
success() {}
})
} catch (e) {
console.error(e);
}
this.showPicker();
},
beforeDestroy() {
if (this.isRecording) {
this.recordEnd(true);
}
this.stopVoice();
this.pauseTime();
},
onReady() {
console.log('onReady');
},
methods: {
//-----------------------------------------------------------------------------------------------
//
// action
//
//-----------------------------------------------------------------------------------------------
//组件数据初始化 进入时、关闭时调用初始化
initValue() {},
//显示组件
showPicker() {
this.recordReset();
this.initSound();
//this.$emit('open');
},
//关闭组件
closePicker() {
//点遮罩 点取消关闭说明用户不想修改,所以这里对数据进行初始化
//this.initValue();
if (this.isRecording) {
this.recordEnd();
}
this.destorySound();
this.stopVoice();
//this.$emit('close');
},
recordReset: function() {
this.playPath = ""; //音频地址
this.stopVoice();
this.resetTime();
},
recordStart: function() {
let _this = this;
console.log('recordStart', this.recorderManager);
_this.resetTime();
// 开始录音
this.recorderManager.start({
duration: this.maxTime * 1000
});
},
recordPause: function() {
this.recorderManager.pause();
},
recordResume: function() {
this.recorderManager.resume();
},
recordEnd: function(force) {
let recordTime = this.recordTime;
force = !!force;
if (!force && recordTime < this.minTime) {
if (recordTime <= 0) {
//==点击事件==;
return false;
}
//==小于5秒==;
uni.showToast({
title: "不能小于" + this.minTime + "秒,请重新录制",
icon: "none"
})
return false;
}
console.log('recordEnd');
this.recorderManager.stop();
},
playVoice() {
if (this.playPath) {
this.innerAudioContext.src = this.playPath;
this.innerAudioContext.play();
this.playing = 1;
}
},
stopVoice() {
if (this.innerAudioContext) {
this.innerAudioContext.stop();
}
this.playing = 0;
},
//-----------------------------------------------------------------------------------------------
//
// Source
//
//-----------------------------------------------------------------------------------------------
initSound: function() {
console.log('initSound');
this.recorderManager = uni.getRecorderManager(); //录音
this.innerAudioContext = uni.createInnerAudioContext(); //播放
var _this = this;
this.recorderManager.onStart(function() {
console.log('开始录音');
_this.startTime();
_this.isRecording = true;
});
//录音暂停事件
this.recorderManager.onPause(function() {
console.log('录音暂停');
_this.isRecordPaused = true;
_this.pauseTime();
});
// 录音恢复事件
this.recorderManager.onResume(function() {
console.log('录音继续');
_this.isRecordPaused = false;
_this.startTime();
})
//录音停止事件
this.recorderManager.onStop(function(res) {
console.log('开始结束' + JSON.stringify(res));
_this.pauseTime();
_this.isRecording = false;
_this.playPath = res.tempFilePath;
_this.onConfirmHandler();
});
},
destorySound: function() {
if (this.recorderManager) {
this.recorderManager.stop();
}
if (this.innerAudioContext) {
this.innerAudioContext.stop();
}
},
//-----------------------------------------------------------------------------------------------
//
// Timer
//
//-----------------------------------------------------------------------------------------------
resetTime: function() {
this.recordTime = 0;
this.pauseTime();
},
startTime: function() {
let _this = this;
this.pauseTime();
_this.timeObj = setInterval(function() {
_this.recordTime++;
//this.$refs.recordClock.setValue(_this.recordTime / _this.maxTime);
if (_this.recordTime == _this.maxTime) _this.recordEnd();
}, 1000);
},
pauseTime: function() {
clearInterval(this.timeObj);
},
//-----------------------------------------------------------------------------------------------
//
// Draw
//
//-----------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------
//
// handler
//
//-----------------------------------------------------------------------------------------------
/**
* 事件取消
*/
onMoveHandler() {
return false;
},
/**
*
*/
onTouchStartHandler: function() {
console.log('onTouchStartHandler');
// 可以暂停情况下,开始录制
if (this.canPuase) {
if (this.isRecording) {
this.isRecordPaused ? this.recordResume() : this.recordPause();
} else {
this.recordReset();
this.recordStart();
}
} else {
this.recordReset();
}
},
/**
* 长按
*/
onLongpressHandler: function() {
// 不可以暂停情况下,开始录制
if (!this.canPuase) {
this.recordStart();
}
},
/**
* 长按结束
*/
onTouchEndHandler: function() {
if (!this.canPuase) {
this.recordEnd();
}
},
onRecordEndHandler: function() {
this.recordEnd();
},
//点击确定
onConfirmHandler() {
// var data = {},list = {},textStr = "",indexStr = "";
this.$emit('recconfirm', this.playPath)
//确定后更新默认初始值,这样再次进入默认初值就是最后选择的
// this.defaultArr = textStr;
//关闭
this.closePicker();
},
}
}
</script>
<style lang="scss">
.lp-record {
position: relative;
z-index: 99;
.mask {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
}
.conbox {
background: #fff;
}
.c666 {
color: #666;
}
.c999 {
color: #999;
}
.fz28 {
font-size: 28rpx;
}
.fz32 {
font-size: 32rpx;
}
.source-player {
padding: 50rpx 0rpx;
border-bottom: solid 1px #eeeeee;
.head {
text-align: left;
font-size: 30rpx;
margin-bottom: 20rpx;
}
}
.record {
text-align: center;
.time {
text-align: center;
font-size: 60rpx;
color: #000;
line-height: 100rpx;
margin-top: 50rpx;
}
.domess {
margin-bottom: 50rpx;
}
.record-box {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.btn {
display: flex;
justify-content: center;
align-items: center;
width: 90rpx;
height: 90rpx;
border-radius: 50%;
color: #fff;
background-color: $uni-color-primary;
text {
font-size: 40rpx;
}
}
.recording {
//position: absolute;
//top: 10px;
//left: 10px;
margin: 20rpx;
width: 80px;
height: 80px;
background-color: $uni-color-error;
z-index: 100;
font-size: 35px;
}
.stop {}
.play {
margin-left: 5rpx;
}
.confirm {}
}
}
.flash {
animation: 2s opacity2 0s infinite;
-webkit-animation: 2s opacity2 0s infinite;
-moz-animation: 2s opacity2 0s infinite;
}
[@keyframes](/user/keyframes) opacity2 {
0% {
opacity: 0.1
}
50% {
opacity: .8;
}
100% {
opacity: 0.1;
}
}
[@-webkit-keyframes](/user/-webkit-keyframes) opacity2 {
0% {
opacity: 0.1
}
50% {
opacity: .8;
}
100% {
opacity: 0.1;
}
}
[@-moz-keyframes](/user/-moz-keyframes) opacity2 {
0% {
opacity: 0.1
}
50% {
opacity: .8;
}
100% {
opacity: 0.1;
}
}
更多关于uni-app 微信小程序在iphone手机上交替播放(暂停)音频和录音(暂停)功能时,导致部分录音丢失的实战教程也可以访问 https://www.itying.com/category-93-b0.html
5 回复
到底是微信小程序还是 APP?如果确认是微信小程序的话,需反馈到微信小程序社区。
更多关于uni-app 微信小程序在iphone手机上交替播放(暂停)音频和录音(暂停)功能时,导致部分录音丢失的实战教程也可以访问 https://www.itying.com/category-93-b0.html
微信小程序,APP我没有测过
微信社区需要原生代码,我现在是uniapp代码怎么给他们反馈?https://developers.weixin.qq.com/community/develop/doc/000808deed8378249ee9f82965b800 微信社区好像也有类似的反馈,但到现在也没解决。
回复 wskeee: 原生代码写个最简单的示例分享为代码片段即可。反馈的人多了应该会得到重视。
这个问题是由于iOS系统的音频会话(Audio Session)管理机制导致的。在iOS上,录音和音频播放使用相同的音频硬件资源,当快速交替操作时,系统可能会中断或重置录音会话,导致录音数据丢失。
核心原因分析:
- iOS的音频会话是单例模式,录音和播放会竞争同一音频通道
- 当innerAudioContext播放音频时,会中断当前的录音会话
- 录音暂停/恢复时,如果音频会话被其他操作打断,可能导致录音缓冲区清空
解决方案:
- 使用音频会话管理
// 在录音开始前设置音频会话
uni.setInnerAudioOption({
mixWithOther: false, // 不与系统音频混音
obeyMuteSwitch: false // 不遵循静音开关
})
// 录音管理器配置
this.recorderManager = uni.getRecorderManager()
this.recorderManager.onError((err) => {
console.error('录音错误:', err)
// 错误处理逻辑
})
- 优化操作时序
recordPause: function() {
// 先停止计时器再暂停录音
this.pauseTime()
this.recorderManager.pause()
},
recordResume: function() {
// 确保录音完全恢复后再启动计时
this.recorderManager.resume()
setTimeout(() => {
this.startTime()
}, 100) // 增加延迟确保录音已恢复
}
- 添加状态检查和错误处理
// 在initSound中添加错误监听
this.recorderManager.onError((res) => {
console.error('录音错误:', res)
this.isRecording = false
this.isRecordPaused = false
this.pauseTime()
})
// 在操作前检查状态
onTouchStartHandler: function() {
if (this.playing) {
// 如果正在播放音频,先停止
this.stopVoice()
setTimeout(() => {
this.handleRecordAction()
}, 300) // 等待音频完全停止
} else {
this.handleRecordAction()
}
}
- 使用临时文件缓存
// 在录音暂停时保存临时文件
let tempFiles = []
recordPause: function() {
this.recorderManager.pause()
// 获取当前录音的临时文件
this.recorderManager.onStop((res) => {
tempFiles.push(res.tempFilePath)
})
}

