通过微信小程序实现一个音乐播放器功能,功能如下,实现效果图如下
https://github.com/MrZHLF/wx-music.git
通过分析这个页面,可以把这个页面拆分成三个组件,分别对应歌词组件,进度条组件和下面切换按钮组件
在js文件中定义一写变量值
picUrl:"", 封面图
isPlaying:false, //false不播放,true播放
let musiclist = []
// 正在播放歌曲的index
let nowPlayingIndex = 0
//获取全局位移背景音乐播放器
const backgroundAudioManager = wx.getBackgroundAudioManager()
通过接口请求数据musicId
是从列表传过来的,backgroundAudioManager
是微信小程序提供的一个全局的背景音乐api,通过设置对应的参数值,最重要的就是设置src,表示这个首歌曲的地址,_loadMusicDetail
需要在
_loadMusicDetail(musicId) {
let music = musiclist[nowPlayingIndex]
wx.setNavigationBarTitle({
title:music.name,
})
this.setData({
picUrl: music.al.picUrl,
isPlaying:false
})
wx.showLoading({
title: '歌曲加载中...',
})
wx.cloud.callFunction({
name:'music',
data:{
musicId,
$url:'musicUrl'
}
}).then((res)=>{
let result = JSON.parse(res.result)
//设置全局背景音乐播放器
if(result.data[0].url == null) {
wx.showToast({
title: '无权限播放',
})
return
}
if(!this.data.isSame) {
// 如果不是同一首歌曲的话,设置播放属性,
backgroundAudioManager.src = result.data[0].url
backgroundAudioManager.title = music.name
backgroundAudioManager.coverImgUrl = music.al.picUrl
backgroundAudioManager.singer = music.ar[0].name
backgroundAudioManager.epname = music.al.name
}
this.setData({
isPlaying: true
})
wx.hideLoading()
// 加载歌词
},
isPlaying
状态值根据这个值来动态的切换播放按钮和暂停按钮已经图片旋转功能,
togglePlaying(){
// 播放事件
if(this.data.isPlaying) {
//正在播放,点击暂停
backgroundAudioManager.pause()
} else {
// 点击播放
backgroundAudioManager.play()
}
this.setData({
isPlaying: !this.data.isPlaying
})
},
根据nowPlayingIndex
来判断当前的索引值,点击的时候-1,当点击到第一首的时候,再次点击,选择的是最后的一首
onPrev(){
// 上一首
nowPlayingIndex--
if(nowPlayingIndex<0) {
// 播放最后一个
nowPlayingIndex=musiclist.length -1
}
this._loadMusicDetail(musiclist[nowPlayingIndex].id)
},
每次点击的时候,都索引值+1,判断代当前的索引值如果等会数据的长度,点击的时候,让索引值赋值为0,从0开始再次点击播放
onNext(){
// 下一首
nowPlayingIndex++
if(nowPlayingIndex===musiclist.length) {
// 如果切换了最后一首之后,在切换,返回第一个
nowPlayingIndex=0
}
this._loadMusicDetail(musiclist[nowPlayingIndex].id)
},
在进度条这个功能,需要拿到每一首歌曲的总时间,根据播放时间,滑动的距离,如果当歌曲播放完之后,自动切换下一首歌曲。
这是一个抽离出来的组件,如果想使用,需要在父组件引入
在这个components组件中,在data定义好开始时间和总时间和播放进度已经距离
data: {
showTime:{
currentTime:"00:00",
totalTime: "00:00"
},
movableDis:0,
progress:0, //进度
},
{{showTime.currentTime}}
{{showTime.totalTime}}
首先在json文件引入
{
"usingComponents": {
"x-progress-bar": "/components/progress-bar/progress-bar"
}
}
然后使用
因为每个手机型号不同,所以要动态获取宽度,以便于后面的计算
ready
调用,_getMovableDis(){
//获取宽度
const query = this.createSelectorQuery()
query.select('.movable-area').boundingClientRect()
query.select('.movable-view').boundingClientRect()
query.exec((rect) =>{
movableAreaWidth = rect[0].width
movableViewWidth=rect[1].width
})
},
1.计算除每一首音乐的歌曲的时间,定义一个用于接受歌曲音乐的总时长和获取全局背景音乐的方法
const backgroundAudioManager = wx.getBackgroundAudioManager() //全局背景音乐
let duration = 0 // 当前歌曲的总时长,以秒为单位
_setTime(){
//算播放总时长
duration= backgroundAudioManager.duration //获取播放总时长
const durationFmt=this._dateFormat(duration)
this.setData({
['showTime.totalTime']: `${durationFmt.min}:${durationFmt.sec}`
})
},
计算的时间都是秒,这个时候需要转换成分钟
_dateFormat(sec){
//格式化时间
const min = Math.floor(sec/60) //分钟
sec = Math.floor(sec % 60) //秒
return {
'min': this._parse0(min),
'sec': this._parse0(sec)
}
},
有的我们是希望格式是```00:00``这种格式,这个时候我们需要对个位数补0
_parse0(sec) {
// 补0
return sec < 10 ? '0' + sec : sec
}
时间计算完之后,需要在backgroundAudioManager.onCanplay
调用,这个时候需要对这个时间判断一下,有个别机型会返回undefined
,如果等于undefined
的时候。需要过一秒钟的时候重新在加载一次,解决时间无法出来的问题
backgroundAudioManager.onCanplay(() => {
// 监听背景音频进入可播放状态事件。 但不保证后面可以流畅播放
if (typeof backgroundAudioManager.duration != 'undefined') {
//获取音频总时间
this._setTime()
} else {
setTimeout(()=>{
this._setTime()
},1000)
}
})
做到这个的时候,我们也拿到的歌曲的每一条的总时间,这个时候我们就要开始播放音乐的时候,开始播放的时间也要计算出来,并且进度条也要随着音乐而加载进度
backgroundAudioManager.onTimeUpdate(() => {
if (!isMoving){
const currentTime = backgroundAudioManager.currentTime //当前播放进度时间
const duration = backgroundAudioManager.duration //总时长
const sec = currentTime.toString().split('.')[0]
if (sec != currentSec) {
// 判断时间是否有想等的
const currentFmt = this._dateFormat(currentTime)
this.setData({
movableDis: (movableAreaWidth - movableViewWidth) * currentTime / duration,
progress: currentTime / duration * 100,
['showTime.currentTime']: `${currentFmt.min}:${currentFmt.sec}`
})
currentSec = sec
}
}
})
手拖拽的时候,会触发touch
事件,根据这个事件,计算出X坐标,赋值给progress
,
onChange(event){
// 移动
if(event.detail.source=='touch') {
this.data.progress=event.detail.x / (movableAreaWidth-movableViewWidth) * 100
this.data.movableDis =event.detail.x
isMoving=true
}
},
当手离开的时候,这个时候我只需要调用backgroundAudioManager.seek
这个api即刻
onTouchEnd(){
// 松开
const currentTimeFmt = this._dateFormat(Math.floor(backgroundAudioManager.currentTime))
this.setData({
progress:this.data.progress,
movableDis:this.data.movableDis,
['showTime.currentTime']: currentTimeFmt.min + ':' + currentTimeFmt.sec
})
backgroundAudioManager.seek(duration*this.data.progress / 100)
isMoving=false
},
页面布局
{{item.lrc}}
这个时候需要在父组件,把请求的接口数据,传递给歌词组件,当页面一进来的时候,就需要调用observers
初始化,
observers:{
lyric(lrc) {
if (lrc =='暂无歌词') {
this.setData({
lrcList:[
{
lrc,
time:0
}
],
nowLyricIndex:-1
})
} else {
this._parseLyric(lrc)
}
}
},
// 解析歌词
_parseLyric(sLyric) {
let line = sLyric.split('\n')
let _lrcList=[]
line.forEach((elem) =>{
let time = elem.match(/\[(\d{2,}):(\d{2})(?:\.(\d{2,3}))?]/g)
if(time != null) {
let lrc = elem.split(time)[1] //获取到歌词
let timeReg = time[0].match(/(\d{2,}):(\d{2})(?:\.(\d{2,3}))?/) //获取到时间
// 吧时间转换成秒
let time2Senconds = parseInt(timeReg[1]) * 60 + parseInt(timeReg[2]) + parseInt(timeReg[3]) / 1000
_lrcList.push({
lrc,
time: time2Senconds
})
}
})
this.setData({
lrcList: _lrcList
})
}
update(currentTime){
// 歌词高亮 从父组件拿到值
let lrcList = this.data.lrcList
if (lrcList.length == 0) {
return
}
// 歌词滚动
if (currentTime > lrcList[lrcList.length-1].time) {
if(this.data.nowLyricIndex!=-1) {
this.setData({
nowLyricIndex:-1,
scrollTop:lrcList.length * lyricHeight
})
}
}
for(let i=0,len=lrcList.length;i
lifetimes:{
ready(){
wx.getSystemInfo({
success(res) {
// 计算除1rpx的大下
lyricHeight = res.screenWidth / 750 * 64
},
})
}
},