uni-app小程序实现录音计时、播放、暂停等功能

其实之前写过一个简单的录音过程,就是录音、暂停、上传即可,也没有样式 uni-app实现录音及播放功能

 

这篇博客实现的东西都在图内,录音,计时开始录音、保存、取消及音频展示等。。。

例图

录音前

在第一次点击录音的时候会先获取录音权限,同意即可。

录音

暂停录音(这里的时间是截图了多段录音)

点击中间的位置可以继续录音

点击保存

这个是点击保存后的展示

 

目前实现的是这个样子,录音,计时,暂停,继续录音,展示音频,可进行播放,及删除等操作

整体思路

其实看图也可以看到:

  1. 点击录音的时候开始录音,并展示定时(第二张图)
  2. 可进行取消、暂停、保存等操作
  3. 暂停显示(图三)
  4. 点击保存后显示(如图四)
  5. 图四的音频可进行播放、删除,并展示了 音频时长

大体就是这么多了,挨个就不解释了(其实最简单的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>

插件改动很小,需要和你拉下来的代码对比一下进行查看,图也在上方

 

很多开发内的细节都在代码内了,有什么问题欢迎私信和留言!!!

 

 

  • 8
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
要在 uni-app 小程序实现环形倒计时,可以使用 uni-canvas 组件结合 JavaScript 实现。以下是一个简单的实现过程: 1. 在页面添加一个 uni-canvas 组件,设置宽度和高度,并设置 canvas-id 属性,例如: ``` <uni-canvas canvas-id="countdown" style="width: 200px; height: 200px;"></uni-canvas> ``` 2. 在页面的 JavaScript ,获取到 canvas 绘图上下文对象,并设置绘图相关属性,例如: ``` const ctx = uni.createCanvasContext('countdown', this); const radius = 80; // 环形半径 const lineWidth = 10; // 环形线宽 const countDownTime = 60; // 倒计时时间,单位为秒 let remainingTime = countDownTime; // 剩余时间 const timer = setInterval(() => { remainingTime--; drawCountDown(remainingTime); if (remainingTime <= 0) { clearInterval(timer); } }, 1000); function drawCountDown(remainingTime) { const angle = (2 * Math.PI / countDownTime) * (countDownTime - remainingTime); ctx.clearRect(0, 0, 200, 200); // 清空画布 ctx.beginPath(); ctx.arc(100, 100, radius, -Math.PI / 2, angle - Math.PI / 2, false); ctx.setStrokeStyle('#ff0000'); ctx.setLineWidth(lineWidth); ctx.stroke(); ctx.closePath(); ctx.draw(); } ``` 3. 在 drawCountDown 函数,根据剩余时间计算出当前的环形绘制角度,并绘制环形。其,使用 arc 方法绘制环形,设置起始角度为 -Math.PI / 2,结束角度为当前角度减去 -Math.PI / 2,圆心坐标为 (100, 100)。 4. 使用 setInterval 方法每隔 1 秒钟更新一次剩余时间,并重新绘制环形,直到倒计时结束。 以上是一个简单的 uni-app 小程序实现环形倒计时的过程,你可以根据需要进行进一步的优化和美化。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值