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

网上看到别的小程序播放音效还需要钱,定眼一看,这不很简单吗?一个分类、分类下放音频的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】,欢迎大家订阅,我会同步文章到公众号上。

posted @ 2025-01-02 18:23  一方_self  阅读(20)  评论(0编辑  收藏  举报