赞
踩
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" safeAreaInsetBottom mode="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、以上代码可以直接粘贴复制到项目即可使用
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。