其实之前写过一个简单的录音过程,就是录音、暂停、上传即可,也没有样式 uni-app实现录音及播放功能
这篇博客实现的东西都在图内,录音,计时开始录音、保存、取消及音频展示等。。。
例图
录音前
在第一次点击录音的时候会先获取录音权限,同意即可。
录音
暂停录音(这里的时间是截图了多段录音)
点击中间的位置可以继续录音
点击保存
这个是点击保存后的展示
目前实现的是这个样子,录音,计时,暂停,继续录音,展示音频,可进行播放,及删除等操作
整体思路
其实看图也可以看到:
- 点击录音的时候开始录音,并展示定时(第二张图)
- 可进行取消、暂停、保存等操作
- 暂停显示(图三)
- 点击保存后显示(如图四)
- 图四的音频可进行播放、删除,并展示了 音频时长
大体就是这么多了,挨个就不解释了(其实最简单的uni-app实现录音及播放功能这个能看懂,这篇就是个升级版)
全部代码
<template>
<view class="page quenaire">
<view class="question-box">
<view class="audioShow">
<luch-audio
v-if="audioContent && startRecording == 0"
:src="audioContent"
:play.sync="audioPlayNew"
@delete="audioDelete"
:audioDuration = "audioDuration"
></luch-audio>
<view class="recordBegin showpic" @tap="startRecord" v-if="startRecording == 0">
<image src="../../static/imgs/pagesTask/record.png"></image>
<text>录音</text>
</view>
<view class="timecountShow" v-if="startRecording == 1">
{{timecount}}
</view>
<view class="recordingShow" v-if="startRecording == 1">
<view class="cancelBtn" @tap="endRecord">
<image src="../../static/imgs/pagesTask/cancel.png"></image><br>
<text>取消</text>
</view>
<image v-if="isZant == false" src="../../static/imgs/pagesTask/recording.png" @tap="endRecordPic"></image>
<image v-else src="../../static/imgs/pagesTask/zant.png" @tap="startRecordPic"></image>
<view class="saveBtn" @tap="saveRecord">
<image src="../../static/imgs/pagesTask/save.png"></image><br>
<text>保存</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import luchAudio from '@/components/luch-audio/luch-audio.vue'
export default {
components:{
luchAudio
},
data() {
return {
audioPlayNew: false,
content: '',
checkList: [],
audioContent: '',
audioName: '',
recorderManager: {},
innerAudioContext: {},
startRecording: 0,
timecount: '00:00:00',
hour: 0,
minute: 0,
second: 0,
timer:'',
isZant: false,
audioDuration: '',
}
},
onLoad(options) {
this.recorderManager = wx.getRecorderManager();
this.innerAudioContext = wx.createInnerAudioContext();
// 为了防止苹果手机静音无法播放
uni.setInnerAudioOption({
obeyMuteSwitch: false
})
this.innerAudioContext.autoplay = true;
console.log("uni.getRecorderManager()",uni.getRecorderManager())
let self = this;
this.recorderManager.onStop(function (res) {
self.audioDuration = res.duration;
self.voicePath = res.tempFilePath;
});
},
methods: {
// 音频删除
audioDelete(obj){
if(obj == true){ // 删除
uni.showModal({
content: '是否确认删除当前音频?',
success: ({cancel, confirm}) => {
if(confirm){
this.audioContent = '';
}
}
})
}
},
// 点击开始录音就开始计时
startRecord() {
this.startRecording = 1;
console.log('开始录音');
this.timecount = '00:00:00';
this.hour = 0;
this.minute = 0;
this.second = 0;
this.getTimeInterval();
const options = {
duration: 600000,
sampleRate: 44100,
numberOfChannels: 1,
encodeBitRate: 192000,
format: 'aac',
frameSize: 50
}
console.log('options',options);
// this.recorderManager.start();
this.recorderManager.start(options);
this.isZant = false;
},
// 计时器
getTimeInterval(){
clearInterval(this.timer);
this.timer = setInterval(()=> {
this.second += 1;
if(this.second >= 60){
this.minute += 1;
this.second = 0;
}
if(this.minute >= 60 && this.second >= 60){
this.minute += 0;
this.hour += 1;
}
this.timecount = this.showNum(this.hour)+":"+this.showNum(this.minute)+":"+this.showNum(this.second);
console.log("this.timecount",this.timecount)
},1000);
},
showNum(num) {
if (num < 10) {
return '0' + num
}
return num
},
// 取消录音
endRecord() {
console.log('录音结束');
this.recorderManager.stop();
clearInterval(this.timer);
this.startRecording = 0;
this.timecount = '00:00:00';
this.hour = 0;
this.minute = 0;
this.second = 0;
},
// 中间图片点击的暂停 点击暂停时同时关闭定时任务
endRecordPic(){
console.log('录音暂停');
this.recorderManager.pause();
clearInterval(this.timer);
this.isZant = !this.isZant;
},
// 中间图片点击开始
startRecordPic(){
console.log('重新开始录音');
this.recorderManager.resume();
this.getTimeInterval();
this.isZant = !this.isZant;
},
// 保存(暂停定时加上传)
saveRecord(){
this.recorderManager.stop();
this.isZant = true;
clearInterval(this.timer);
this.audioContent == '';
setTimeout(() => {
this.audioAdd();
},300);
},
playVoice() {
console.log('播放录音');
console.log('this.voicePath',this.voicePath);
if (this.voicePath) {
this.innerAudioContext.src = this.voicePath;
this.innerAudioContext.play();
}
},
// 上传录音文件
audioAdd() {
console.log("this.voicePath",this.voicePath)
uni.showLoading({
title: '保存中...'
});
if(this.voicePath){
uni.uploadFile({
url: this.baseUrl, //仅为示例,非真实的接口地址
filePath: this.voicePath,
name: 'file',
success: (res) => {
uni.hideLoading();
JSON.parse(res.data) && this.$toast("保存成功!");
this.audioContent = JSON.parse(res.data).result.visiturl;
this.startRecording = 0;
},
fail: (err) => {
uni.hideLoading();
}
});
}else{
console.log("录音失败")
this.$toast("录音失败!");
uni.hideLoading();
}
},
},
}
</script>
<style lang="less" scoped>
.page{
background-image: linear-gradient(#0063FF, #009CFF);
}
.quenaire{
padding: 0 30rpx;
.question-box{
background-color: #FFFFFF;
border: 6rpx solid #009FFF;
border-radius: 16rpx;
margin-top: -40rpx;
position: relative;
z-index: 2;
padding: 30rpx;
width:620rpx;
}
.audioShow{
.recordBegin{
image{
width: 140rpx;
height: 140rpx;
}
text{
color: #FFFFFF;
margin-left: -100rpx;
position: relative;
top: -55rpx;
}
}
.recordingShow{
display: flex;
justify-content: space-around;
font-size: 22rpx;
color: #F1AF00;
image{
&:nth-child(1), &:nth-child(3){
width: 56rpx;
height: 56rpx;
}
&:nth-child(2){
width: 140rpx;
height: 140rpx;
}
}
.cancelBtn, .saveBtn{
margin-top: 40rpx;
width: 56rpx;
text-align: center;
}
.cancelBtn{
margin-left: 40rpx;
}
.saveBtn{
margin-right: 40rpx;
}
}
.timecountShow{
color: #B2B2B2;
font-size: 50rpx;
text-align: center;
margin: 60rpx 0;
}
}
.audioPlay{
margin-top: 40rpx;
button{
font-size: 34rpx;
width: 40%;
height: 80rpx;
line-height: 80rpx;
margin-bottom: 30rpx;
}
}
}
</style>
因为引入了插件 luch-audio,但是对插件有改动(下面有具体链接,如果会使用HBuilderX,这一步也没啥问题)
内部的音频展示组件使用了DCloud插件市场内的插件 luch-audio api封装audio音频组件 - DCloud 插件市场,链接可进行查看插件,因为插件实现的较早,而且也需要修改部分东西,所以需要先下载下来,引入到需要使用的文件内
在这一部分是需要先下载插件的,这里使用的是uni-app,是通过HBuilderX来导入插件的
插件luch-audio的改动
之前的样式可以去插件市场看,现在我改动后的是这样的
<template>
<view class="audio-warp">
<view class="cover-warp" :class="{ hasbg: !poster }">
<image src="../../static/imgs/pagesTask/audio.png" mode="widthFix" style="width: 100%;height: 100%;"></image>
</view>
<view class="audio-con">
<view class="info">
<text class="audio-title am-text-eill">{{ name }}</text>
<text class="audio-author am-text-eill" v-if="showCurrentTime == false">{{duration}}</text>
<text class="audio-author am-text-eill" v-else>{{audioTimeUpdate}}</text>
</view>
<view class="playbtn" :class="{ pause: play }" @click="handleBtnClick"></view>
<view class="delebtn" @click="handleDel">
<image src="../../static/imgs/pagesTask/audiodel.png" mode="widthFix"></image>
</view>
</view>
</view>
</template>
<script>
/**
* luch-audio 0.0.1
* @module luch-audio
* @Author lu-ch
* @Date 2019-06-11
* @Email webwork.s@qq.com
* @description 音频播放组件,使用了createInnerAudioContext
*/
/**
* Props itemsProps
* @prop {Boolean} play - 是否播放,双向绑定,绑定时需使用.sync 如果为true 则播放,为false 则暂停
* ... 其他api同文档 (https://uniapp.dcloud.io/api/media/audio-context?id=createinneraudiocontext)
*/
/**
* 将秒转换为 分:秒
* @param {Number} s - 秒数
*/
function sToHs(s) {
//计算分钟
//算法:将秒数除以60,然后下舍入,既得到分钟数
let h;
h = Math.floor(s / 60);
//计算秒
//算法:取得秒%60的余数,既得到秒数
s = s % 60;
//将变量转换为字符串
h += '';
s += '';
//如果只有一位数,前面增加一个0
h = (h.length === 1) ? '0' + h : h;
s = (s.length === 1) ? '0' + s : s;
return h + ':' + s;
}
export default {
name: 'ComAudio',
props: {
play: {
type: Boolean,
required: true
},
src: {
type: String
},
poster: {
type: String,
default: ''
},
name: {
type: String,
default: '音频1'
},
author: {
type: String,
default: '未知作者'
},
autoplay: {
type: Boolean,
default: false
},
loop: {
type: Boolean,
default: false
},
obeyMuteSwitch: {
type: Boolean,
default: true
},
audioDuration: {
type: Number
}
},
data() {
return {
audioTimeUpdate: '00:00',
innerAudioContext: '',
duration: '00:00' ,
showCurrentTime: false
};
},
mounted() {
this.$nextTick(() => {
console.log("this.audioDuration",this.audioDuration);
if(this.audioDuration == 0){
this.duration = '00:00';
}else{
this.duration = sToHs(Math.floor(this.audioDuration/1000));
}
})
},
methods: {
audioPlay() {
this.$emit('update:play', true);
},
audioPause() {
this.$emit('update:play', false);
},
handleBtnClick() {
this.showCurrentTime = true;
this.$emit('update:play', !this.play);
},
handleDel(){
this.$emit('delete', true);
},
contextInit() {
console.log('创建');
let that = this;
that.$emit('update:play', false);
let innerAudioContext = uni.createInnerAudioContext();
innerAudioContext.autoplay = that.autoplay;
innerAudioContext.src = that.src;
innerAudioContext.loop = that.loop;
innerAudioContext.obeyMuteSwitch = that.obeyMuteSwitch;
innerAudioContext.onPlay(function() {
that.audioTimeUpdate = sToHs(Math.floor(innerAudioContext.currentTime));
that.audioPlay();
});
innerAudioContext.onPause(function() {
that.audioPause();
});
innerAudioContext.onEnded(function() {
this.showCurrentTime = false;
that.audioPause();
});
innerAudioContext.onTimeUpdate(function() {
that.audioTimeUpdate = sToHs(Math.floor(innerAudioContext.currentTime));
});
innerAudioContext.onError(err => {
console.log(err);
});
this.innerAudioContext = innerAudioContext;
},
},
watch: {
play(n) {
if (n) {
this.innerAudioContext.play();
} else {
this.innerAudioContext.pause();
}
},
src() {
this.innerAudioContext.destroy();
this.contextInit();
},
audioDuration(val){
console.log("音频111",val);
this.duration = sToHs(Math.floor(val/1000));
}
},
created() {
this.contextInit();
},
beforeDestroy() {
this.innerAudioContext.destroy();
}
};
</script>
<style lang="scss">
.am-text-eill {
/*超出省略号*/
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.audio-warp {
display: flex;
overflow: hidden;
// height: 67px;
height: 62px;
border-radius: 3px;
border: 1px solid #e0e0e0;
}
.cover-warp {
position: relative;
flex-shrink: 0;
width: 65px;
height: 100%;
&.hasbg {
background-color: #e6e6e6;
}
.cover-img {
width: 100%;
height: 100%;
}
}
.audio-con {
position: relative;
flex: 1;
display: flex;
width: 0;
padding: 0 15px;
align-items: center;
background-color: #fcfcfc;
.info {
width: 100%;
}
.audio-title {
display: block;
padding-bottom: 7px;
padding-right: 25px;
font-size: 14px;
color: #353535;
}
.audio-author {
display: block;
font-size: 12px;
color: #888888;
}
.audio-time {
position: absolute;
right: 15px;
top: 6px;
font-size: 13px;
color: #9d9d9d;
}
.playbtn{
position: absolute;
left: 65%;
top: 50%;
width: 58rpx;
height: 58rpx;
transform: translateX(-50%) translateY(-50%);
border-radius: 50%;
background-size: 100% 100%;
background-image: url(../../static/imgs/pagesTask/audioplay.png);
&.pause {
background-image: url(../../static/imgs/pagesTask/audioparse.png);
}
}
.delebtn{
position: relative;
left: -30rpx;
top: 8rpx;
image{
width: 30rpx;
height: 28rpx;
}
}
}
</style>
插件改动很小,需要和你拉下来的代码对比一下进行查看,图也在上方
很多开发内的细节都在代码内了,有什么问题欢迎私信和留言!!!