uni-app中实现音乐播放器
1、主要利用的是uni-app中提供的uni.createInnerAudioContext()来进行实现;
2、代码示例
(1)主页面代码展示
<template><view class="music-layout"><view class="tn-flex"><view class="left-content"><view class="left-pic-layout"><img :src="bgUrl" :class="isPlay ? 'img-rotate' : ''"><view class="small-circle"></view></view><view class="like-layout"><text class="tn-icon-like-fill" v-if="musicDetail.liked" @click="onLikeMusic(false, musicDetail)"></text><text class="tn-icon-like" v-else @click="onLikeMusic(true, musicDetail)"></text></view></view><view class="right-content"><view class="song-name-layout"><view>{{ musicDetail.bsmb002 }}</view><view v-if="isPlay"><PlayerAnimation></PlayerAnimation></view></view><view class="progress-layout"><text>{{ formatTime(musicCurrentTime) }}</text><tn-slider:min="0":max="musicTotalTime"class="progress"v-model="musicCurrentTime"inactiveColor="#EAEAEA"activeColor="#FF3370":blockWidth="1":lineHeight="4"></tn-slider><text>{{formatTime(musicTotalTime)}}</text></view><view class="actions-layout"><view class="toggle-type-layout" @click="handleToggleType"><img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/loop-black-icon.png" v-if="toggleType == 1" ><img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/single-black-icon.png" v-else-if="toggleType == 2"><img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/random-black-icon.png" v-else></view><view class="action-center"><img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/previous-icon.png" class="previous-icon" @click="handlePreviousPlay"><img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/play-icon.png" v-if="isPlay" class="player-icon" @click="handlePlay(false)"><img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/stop-icon.png" v-else class="player-icon" @click="handlePlay(true)"><img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/next-icon.png" class="next-icon" @click="handleNextPlay"></view><img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/music-list.png" class="music-list-icon" @click="handleMusicListShow"></view></view></view><view class="add-music" @click="handleAddWisk">点这里添加您的音乐心愿单~</view></view>
</template><script>import { formatTime } from '@/utils/util'; // 代码在下方对应文件中
import PlayerAnimation from "../../../components/player-animation/player-animation.vue"; // 播放效果动画(代码在下方对应文件中)
import { musicList, saveCollect, cancleCollect } from "@/api/fetusMovement"; // 接口
import { mediaUrl } from '@/utils/env'; // 静态资源地址前缀export default {components: {PlayerAnimation},data() {return {isPlay: false, // 是否播放toggleType: 1, // 播放顺序 (1:循环;2:单曲循环;3:随机)musicDetail : { }, // 音乐详情current: null, // 当前播放的是哪首currentSrc: '', // 当前音乐路径musicPlayCtx: null, // 音频上下文musicCurrentTime: 0, // 音乐当前播放进度musicTotalTime: 100, // 音乐总的时间musicList: [], // 音乐列表bgUrl: 'http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/tjyy_bg.png',timers: null,openId: ''}},watch: {currentSrc: {handler() {if(this.musicPlayCtx) {this.musicPlayCtx.destroy();}this.musicPlayCtx = uni.createInnerAudioContext();this.musicPlayCtx.obeyMuteSwitch = false;this.musicCurrentTime = 0;this.musicPlayCtx.src = encodeURI(mediaUrl+ this.musicDetail.bsmb007);this.musicTotalTime = this.musicDetail.bsmb009;if(this.isPlay){this.handleAudioPlay();}},immediate: true,deep: true,}},mounted() {this.openId = getApp().globalData.openId;this.getMusicList();},methods: {// 打开音乐列表getMusicList() {musicList(this.openId).then(res => {if (res.success) {this.musicList = res.result;this.current = 0;this.musicDetail = this.musicList[this.current];this.currentSrc = encodeURI(mediaUrl+ this.musicDetail.bsmb007);}})},// 播放列表弹窗显示handleMusicListShow() {const { isPlay, toggleType, musicList, current} = this;this.$emit('handleMusicListShow', {bool: true,isPlay,toggleType,musicList,current});},formatTime,// 处理播放handlePlay(bool) {this.isPlay = bool;if(bool) {this.musicPlayCtx.seek(this.musicCurrentTime + 1);this.handleAudioPlay();} else {this.musicPlayCtx.stop();}},// 播放handleAudioPlay() {this.musicPlayCtx.play();if(this.timers) {clearTimeout(this.timers);}this.timers = setTimeout(() => {console.log(this.musicPlayCtx.paused);}, 200);// 播放完成this.musicPlayCtx.onEnded(() => {if(this.toggleType == 1) {this.handleNextPlay(); // 列表循环} else if(this.toggleType == 2) {this.musicPlayCtx.play(); // 单曲循环} else {this.current = Math.floor(Math.random() * this.musicList.length);this.musicDetail = this.musicList[this.current];this.currentSrc = encodeURI(mediaUrl+ this.musicDetail.bsmb007);}})// 更新播放时间this.musicPlayCtx.onTimeUpdate(() => {this.onTimeUpdate();})// 播放出现错误this.musicPlayCtx.onError(() => {this.onError();})},// 下一首handleNextPlay() {this.isPlay = true;if(this.current < this.musicList.length - 1) {uni.showToast({title: '即将播放下一首',icon: 'none'})this.current ++;this.musicDetail = this.musicList[this.current];this.currentSrc = encodeURI(mediaUrl+ this.musicDetail.bsmb007);return}uni.showToast({title: '已经为最后一首了',icon: 'none'})},// 上一首handlePreviousPlay() {this.isPlay = true;if(this.current) {this.current --;uni.showToast({title: '即将播放上一首',icon: 'none'})this.musicDetail = this.musicList[this.current];this.currentSrc = encodeURI(mediaUrl+ this.musicDetail.bsmb007);return;}uni.showToast({title: '已经为第一首了',icon: 'none'})},// 播放类型handleToggleType(parmas) {if(parmas) {this.toggleType = parmas;const { isPlay, toggleType, musicList, current} = this;this.$emit('handleMusicListShow', {bool: true,isPlay,toggleType,musicList,current});return;}this.toggleType = this.toggleType == 1 ? 2 : this.toggleType == 2 ? 3 : 1;},// 切换音乐handleMusicChange(index) {this.isPlay = true;this.musicDetail = this.musicList[index];this.current = index;this.currentSrc = encodeURI(mediaUrl+ this.musicDetail.bsmb007);this.handleMusicListShow();},// 加载失败onError() {uni.showToast({title: '音频加载失败',icon: 'none'})},// 时间更新onTimeUpdate() {if (this.musicPlayCtx.currentTime > 0 && this.musicPlayCtx.currentTime <= 1) {this.musicCurrentTime = 1;} else if (this.musicCurrentTime !== Math.floor(this.musicPlayCtx.currentTime)) {this.musicCurrentTime = Math.floor(this.musicPlayCtx.currentTime);}},// 喜欢onLikeMusic(bool, item) {this.musicDetail.liked = bool;if (bool) {//收藏saveCollect({bsmId: item.id,openId: this.openId}).then(res => {if (res.success) {uni.showToast({title: '收藏成功',icon: 'none'})this.getMusicList()}})} else {//取消收藏cancleCollect({bsmId: item.id,openId: this.openId,id: item.collectId}).then(res => {if (res.success) {uni.showToast({title: '已取消收藏',icon: 'none'})this.getMusicList()}})}},// 心愿歌单handleAddWisk() {uni.navigateTo({url: '/toolsPages/fetusMovement/addWish'})}},// 销毁destroyed() {if(this.musicPlayCtx) this.musicPlayCtx.destroy();if(this.timers) clearTimeout(this.timers);}
}
</script><style scoped lang="scss">.music-layout {position: relative;width: 690rpx;padding: 20rpx 30rpx;background: #FFFFFF;border-radius: 30rpx;backdrop-filter: blur(16px);
}.left-content {width: 130rpx;.like-layout {margin-top: 20rpx;> text {color: #FF3370;font-size: 40rpx;}}
}.left-pic-layout {position: relative;width: 100rpx;height: 100rpx;border-radius: 50%;overflow: hidden;img {width: 100%;height: 100%;border-radius: 50%;}.small-circle{position: absolute;left: 50%;top: 50%;transform: translate(-50%, -50%);width: 20rpx;height: 20rpx;background-color: white;border-radius: 50%;}
}.right-content {flex: 1;margin-left: 30rpx;
}.song-name-layout {display: flex;justify-content: space-between;align-items: center;font-size: 32rpx;color: #323A59;line-height: 40rpx;height: 40rpx;text {color: rgba(115, 121, 141, 1);}
}.progress-layout {display: flex;justify-content: space-between;align-items: center;margin: 25rpx 0 0 2rpx;font-size: 26rpx;color: #73798D;line-height: 36rpx;.progress {display: flex;align-items: center;flex: 1;margin: 0 20rpx;}
}.actions-layout {display: flex;align-items: center;justify-content: space-between;margin: 25rpx 0 0 4rpx;.toggle-type-layout {> img {width: 30rpx;height: 26rpx;}}.action-center {display: flex;justify-content: space-between;align-items: center;flex: 1;margin: 0 86rpx;}.previous-icon, .next-icon {width: 30rpx;height: 32rpx;}.player-icon {width: 42rpx;height: 42rpx;}.music-list-icon {width: 30rpx;height: 30rpx;}
}.img-rotate {transform-origin: center center;animation: rotate 5s infinite linear; /* 实现旋转动画效果 */
}@keyframes rotate {from {transform: rotate(0deg);}to {transform: rotate(360deg);}
}.add-music {margin-top: 20rpx;text-align: center;font-size: 20rpx;color: #999999;text-decoration: underline #999999;
}
</style>
(2)utils中util.js文件代码
function fixedZero(val) {return val * 1 < 10 ? `0${val}` : val;
}const formatTime = (time) => {const minutes = 60;const m = Math.floor(time / minutes);const s = Math.floor(time % 60);return `${fixedZero(m)}:${fixedZero(s)}`;
}
(3)components中player-animation文件夹player-animation.vue文件代码
<template><view class="loading"><view class="item"></view><view class="item"></view><view class="item"></view><view class="item"></view><view class="item"></view></view>
</template><script>
export default {name: "player-animation"
}
</script><style scoped>
/* 设置位置 */
.loading {height: 24rpx;display: flex;align-items: center;
}.item {height: 24rpx;width: 2rpx;background: #FF3370;margin: 0 3rpx;border-radius: 10rpx;animation: loading 2s infinite;
}/* 设置动画 */
@keyframes loading {0% {height: 0;}50% {height: 24rpx;}100% {height: 0;}
}/* 为每一个竖条设置延时 */
.item:nth-child(2) {animation-delay: 0.2s;
}.item:nth-child(3) {animation-delay: 0.4s;
}.item:nth-child(4) {animation-delay: 0.6s;
}.item:nth-child(5) {animation-delay: 0.8s;
}</style>
(4)播放器列表弹窗代码
<template><tn-popup v-model="show"safeAreaInsetBottommode="bottom"height="1200rpx"@close="handleClose"><view class="music-container"><view class="header-layout"><view class="header-left">胎教音乐列表<text>({{ musicLst.length }}首)</text></view><view class="header-right" @click="() => handleToggle()"><img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/loop-icon.png" v-if="toggleType == 1" ><img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/single-icon.png" v-else-if="toggleType == 2"><img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/random-icon.png" v-else>顺序播放</view></view><scroll-view scroll-y style="height: 1050rpx;"><view class="music-list"><view :class="['music-item', current == index && isPlay ? 'active' : '']"v-for="(item, index) in musicLst":key="index"@click="handleItemClick(index)"><view class="name">{{item.bsmb002}}</view><PlayerAnimation v-if="current == index && isPlay"></PlayerAnimation></view></view></scroll-view></view></tn-popup></template><script>
import PlayerAnimation from "../../../components/player-animation/player-animation.vue";
export default {props: {musicLst : {type: Array,default: []},current: {type: Number,default: 0},toggleType:{type:Number,default: 1},isPlay:{type:Boolean,default: false}},components: {PlayerAnimation},data() {return {show: false}},methods: {// 切换歌曲handleItemClick(index) {this.$emit('handleMusicChange', index);},// 关闭handleClose() {this.show = false;},// 播放类型切换handleToggle() {const playType = this.toggleType == 1 ? 2 : this.toggleType == 2 ? 3 : 1;this.$emit('handleToggleType', playType);},}
}
</script><style scoped lang="scss">
.music-container {padding: 0 30rpx 0;.header-layout {display: flex;justify-content: space-between;align-items: center;margin-top: 10rpx;height: 90rpx;}.header-left {display: flex;align-items: center;font-size: 32rpx;font-weight: 500;color: #333333;line-height: 44rpx;text {font-size: 24rpx;font-weight: 400;color: #666666;line-height: 34rpx;}}.header-right {display: flex;align-items: center;height: 56rpx;background: linear-gradient(135deg, #FF74A2 0%, #FF3370 100%);border-radius: 28rpx;padding: 0 12rpx;font-size: 26rpx;color: white;img {margin-right: 10rpx;width: 26rpx;height: 26rpx;}}
}.music-list {font-size: 28rpx;color: #333333;line-height: 40rpx;.music-item {display: flex;align-items: center;justify-content: space-between;padding: 20rpx 0;font-size: 28rpx;color: #333333;line-height: 40rpx;}
}.active {color: #FF3370;
}</style>
3、实现也面向效果展示
(1)播放器
(2)播放器弹窗
4、实现该功能过程所遇到的问题总结
(1)当一首歌曲播完之后,进度条不更新问题,在网上查看到的方法都是说:该问题是存在的一个bug,解决的方案是我们再代码中要主动的调用paused()方法
this.timers = setTimeout(() => {console.log(this.musicPlayCtx.paused);
}, 200);
(2)当点击暂停播放后,音乐从头播放的问题,解决方案:利用seek()方法使其跳转到指定位置
this.musicPlayCtx.seek(this.musicCurrentTime + 1);
(3)音乐播放器的地址,含有中文名称,在模拟器上面可以正常播放,手机上面不可以,解决方案:需要将该地址进行编码,编译成编译器可以识别地址
this.currentSrc = encodeURI(mediaUrl+ this.musicDetail.bsmb007);
5、以上代码可以直接粘贴复制到项目即可使用