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上,录音和音频播放使用相同的音频硬件资源,当快速交替操作时,系统可能会中断或重置录音会话,导致录音数据丢失。

核心原因分析:

  1. iOS的音频会话是单例模式,录音和播放会竞争同一音频通道
  2. 当innerAudioContext播放音频时,会中断当前的录音会话
  3. 录音暂停/恢复时,如果音频会话被其他操作打断,可能导致录音缓冲区清空

解决方案:

  1. 使用音频会话管理
// 在录音开始前设置音频会话
uni.setInnerAudioOption({
  mixWithOther: false, // 不与系统音频混音
  obeyMuteSwitch: false // 不遵循静音开关
})

// 录音管理器配置
this.recorderManager = uni.getRecorderManager()
this.recorderManager.onError((err) => {
  console.error('录音错误:', err)
  // 错误处理逻辑
})
  1. 优化操作时序
recordPause: function() {
  // 先停止计时器再暂停录音
  this.pauseTime()
  this.recorderManager.pause()
},

recordResume: function() {
  // 确保录音完全恢复后再启动计时
  this.recorderManager.resume()
  setTimeout(() => {
    this.startTime()
  }, 100) // 增加延迟确保录音已恢复
}
  1. 添加状态检查和错误处理
// 在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()
  }
}
  1. 使用临时文件缓存
// 在录音暂停时保存临时文件
let tempFiles = []
recordPause: function() {
  this.recorderManager.pause()
  // 获取当前录音的临时文件
  this.recorderManager.onStop((res) => {
    tempFiles.push(res.tempFilePath)
  })
}
回到顶部