在小程序中开发音效盒子功能,公开源码

419 阅读4分钟

网上看到别的小程序播放音效还需要钱,定眼一看,这不很简单吗?一个分类、分类下放音频的url,再播放出来就行,说干就干。最终成品如下:

点击音效的时候,会先出现加载中的loading效果,因为mp3文件是oss上的,需要加载才能播放,播放中时会聚焦是哪个在播放。

播放中效果:

我的技术栈如下:使用uniapp,前端框架为:uview,菜单和音效url均通过接口获取,也可以写死在小程序里面(除非你不想更改)。

我的小程序功能已上线,体验使用请在微信中搜索:《一方云知》,拉到最下面有一个云音盒的菜单,点进去就是:

下面贴上全部代码(247行),基本上稍微改改配置部分就能用了:

<template>
	<view>

		<view v-if="scrollList.length">
			<div class="header ">
				<u-scroll-list :indicator="false">
					<view class="scroll-list" style="flex-direction: row;">
						<view class="scroll-list__goods-item" @click="click_class(index)"
							v-for="(item, index) in scrollList" :key="index"
							:class="[(index === 9) && 'scroll-list__goods-item--no-margin-right']">
							<image class="scroll-list__goods-item__image" :src="item.thumb"></image>
							<text class="scroll-list__goods-item__text "
								:class="{'selected':class_index==index}">{{ item.title }}</text>
						</view>
					</view>
				</u-scroll-list>
			</div>

			<swiper :current="class_index" class="swiper" circular="false" :indicator-dots="false" :autoplay="false"
				@change="swiper_change">
				<swiper-item v-for="(item, index) in scrollList" :key="index">

					<z-paging :show-empty-view-reload="false" :show-refresher-when-reload="false" style="height: 100vh;"
						ref="paging" :fixed="false" :list.sync="item.child" :auto="false" :refresher-enabled="false">
						<div class="bgm_list">
							<div class="bgm_item" :class="{'selected':item2.is_play}"
								v-for="(item2, index2) in item.child" :key="index2" @click="play_mp3(index, index2)">
								<div class="title">
									{{item2.title}}
								</div>
								<div class="icon" v-if="item2.mp3_url">
									<u-icon name="volume-fill" color="black" size="20"
										v-if="!item2.is_play && !item2.loading"></u-icon>
									<u-loading-icon size="20" v-if="item2.loading"></u-loading-icon>
									<image v-if="item2.is_play" class="scroll-list__goods-item__image"
										src="/subpages/tool/static/playing.gif" style="width: 40rpx;height: 40rpx;">
									</image>
								</div>
							</div>
						</div>
						<u-gap height="120" bgColor="#ffffff"></u-gap>
					</z-paging>

				</swiper-item>
			</swiper>
		</view>

		<view v-else style="margin-top: 100rpx;">
			<u-loading-icon></u-loading-icon>
		</view>

	</view>
</template>

<script>
	export default {
		data() {
			return {
				class_index: 0, // 选项分类
				scrollList: [],
				innerAudioContext: null, // 全局mp3播放对象
				prev_index: null, // 上一次播放的mp3一级菜单
				prev_index2: null, // 上一次播放的mp3二级菜单
			};
		},
		onLoad() {
			this.load_config()
		},
		methods: {
			async load_config() {
				this.scrollList = [
					{
						title: '台词', // 菜单标题
						thumb: 'https://cdn.uviewui.com/uview/goods/1.jpg', // 菜单封面
						child: [{
								title: '你已经死了', // 音效标题
								mp3_url: 'https://yifangyunzhi-anime.oss-cn-shanghai.aliyuncs.com/audio/yunyinhe/%E4%BD%A0%E5%B7%B2%E7%BB%8F%E6%AD%BB%E4%BA%86_%E8%80%B3%E8%81%86%E7%BD%91_%5B%E5%A3%B0%E9%9F%B3ID%EF%BC%9A37127%5D.mp3', // 音效url
								is_play: false, // 是否在播放中
								can_play: false, // mp3是否在加载完成 可以播放了,因为有些mp3比较大,加载需要一些时间
							},
							{
								title: '略略略吐舌头',
								mp3_url: 'https://yifangyunzhi-anime.oss-cn-shanghai.aliyuncs.com/audio/yunyinhe/%E7%95%A5%E7%95%A5%E7%95%A5%E5%90%90%E8%88%8C%E5%A4%B4_%E8%80%B3%E8%81%86%E7%BD%91_%5B%E5%A3%B0%E9%9F%B3ID%EF%BC%9A36461%5D.wav'
							},
							{
								title: '汤姆惨叫声',
								mp3_url: 'https://yifangyunzhi-anime.oss-cn-shanghai.aliyuncs.com/audio/yunyinhe/%E6%B1%A4%E5%A7%86%E7%8C%AB_%E8%80%B3%E8%81%86%E7%BD%91_%5B%E5%A3%B0%E9%9F%B3ID%EF%BC%9A37188%5D.mp3'
							},
						],
					}, // 基本数据结构参考
				]
			},
			// 将上一次播放的mp3置为未播放 也就是停止播放状态
			reset_prev_play_state() {
				if (this.prev_index != null && this.prev_index2 != null) {
					this.play_end(this.prev_index, this.prev_index2)
				}
			},
			set_prev_index(index, index2) {
				this.prev_index = index
				this.prev_index2 = index2
			},
			play_mp3(index, index2) {
				const that = this
				const mp3_url = that.scrollList[index].child[index2].mp3_url
				if (!mp3_url) return
				// 多次会调用播放新的文件时,提前销毁实例,可避免-99错误
				this.reset_prev_play_state()
				this.set_prev_index(index, index2)
				if (that.innerAudioContext) {
					try {
						that.innerAudioContext.pause();
						that.innerAudioContext.destroy()
						that.innerAudioContext = null
					} catch (e) {
						// TODO handle the exception
					}
				}
				that.scrollList[index].child[index2].loading = true // 音频正在加载中
				that.innerAudioContext = uni.createInnerAudioContext();
				that.innerAudioContext.autoplay = false;
				that.innerAudioContext.src = mp3_url;
				that.innerAudioContext.onCanplay(() => {
					that.play_start(index, index2)
					// 音频进入可以播放状态,但不保证后面可以流畅播放
					that.scrollList[index].child[index2].loading = false // 音频加载成功
					that.innerAudioContext.play();
					console.log('音频进入可以播放状态');
				});
				that.innerAudioContext.onPlay(() => {
					// 同一个mp3地址只触发一次回调  不知道怎么回事
					console.log('加载完成 开始播放');
				});
				that.innerAudioContext.onError((res) => {
					console.log('播放失败', res.errMsg, res.errCode);
					that.play_end(index, index2)
				});
				that.innerAudioContext.onEnded(() => {
					that.play_end(index, index2)
					// console.log('播放结束');
				});
			},
			play_start(index, index2) {
				this.scrollList[index].child[index2].is_play = true
				this.$forceUpdate()
			},
			play_end(index, index2) {
				this.scrollList[index].child[index2].is_play = false
				this.scrollList[index].child[index2].loading = false // 结束播放 不用加载了
				this.$forceUpdate()
			},
			swiper_change(e) {
				this.class_index = e.detail.current
			},
			// 点击分类
			click_class(index) {
				this.class_index = index
			},
		}
	}
</script>

<style lang="scss" scoped>
	.header {
		padding: 20rpx;
		background-color: #FFDD09;

		.scroll-list {
			@include flex(column);
			padding-bottom: 0 !important;

			&__goods-item {
				margin-right: 40rpx;

				&__image {
					width: 80rpx;
					height: 80rpx;
					border-radius: 8rpx;
					display: block;
				}

				&__text {
					text-align: center;
					font-size: 28rpx;
					margin-top: 10rpx;
					display: inline-block;
					width: 100%;
				}

				.selected {
					color: white;
				}
			}

			&__show-more {
				background-color: #fff0f0;
				border-radius: 6rpx;
				padding: 6rpx 12rpx;
				@include flex(column);
				align-items: center;

				&__text {
					font-size: 24rpx;
					width: 24rpx;
					color: #f56c6c;
					line-height: 32rpx;
				}
			}
		}
	}

	.swiper {
		min-height: 80vh;

		.bgm_list {
			display: flex;
			flex-flow: row wrap;

			.selected {
				color: white;
				background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
			}

			.bgm_item {
				box-sizing: border-box;
				flex-basis: 23.1%;
				margin-left: 10rpx;
				margin-top: 10rpx;
				padding: 20rpx 10rpx;
				border-radius: 10rpx;
				background-color: #F8F8F8;
				display: flex;
				flex-direction: column;
				justify-content: space-between;

				.title {
					text-align: center;
				}

				.icon {
					display: flex;
					justify-content: flex-end;
				}
			}
		}
	}
</style>

代码说明:

在load_config中通过api获取配置,或者直接写死配置也可以的,只不过要改的话只能再重新提审了,其他js代码就是一些菜单点击的状态变化控制了,还有简单的页面样式css。

这篇分享文章就到这里啦!如果你对文章内容有疑问或想要深入讨论,欢迎在评论区留言,我会尽力回答。同时,如果你觉得这篇文章对你有帮助,不妨点个赞并分享给其他同学,让更多人受益。

想要了解更多相关知识,可以查看我以往的文章,其中有许多精彩内容。记得关注我,获取及时更新,我们可以一起学习、讨论技术,共同进步。

感谢你的阅读与支持,期待在未来的文章中与你再次相遇!

我的微信公众号:【xdub】,欢迎大家订阅,我会同步文章到公众号上。