音乐播放的设计实现
具体代码请查看:mygithub
播放需要相关数据
因为很多界面都需要相关的逻辑,所以我是用状态管理工具来抽取播放相关的逻辑与数据,在页面中只显示数据,需要控制时也只是向状态管理工具dispach,我用的库为hy-event-store
数据如下:
state: {
playSongList: [],
playSongIndex: 0,
id: 0,
currentSong: {},
currentTime: 0,
durationTime: 0,
lyricInfos: [],
currentLyricText: "",
currentLyricIndex: -1,
isFirstPlay: true,
isPlaying: false,
playModeIndex: 0, // 0:顺序播放 1:单曲循环 2:随机播放
},
playSongList是播放列表,这个取决与你是怎么进入音乐播放界面的,在不同的地方进入时,会向此stata传递相关的播放列表,当需要播放时,只需要dispach播放函数,传入歌曲id即可
页面播放的渲染
歌曲播放时,页面的数据会随之发生变化,比如currentTime, sliderValue等,我们可以在hy-event-store中监听歌曲的播放,在页面中拿这些改变的数据,随之改变,而有些改变需要做相应逻辑变化,比如进度条就需要:const sliderValue = currentTime / this.data.durationTime * 100,又因为变化其实不用那么频繁,我们可以对其进行适当节流,用underscore库即可
audioContext.onTimeUpdate(() => {
// 1.获取当前播放的时间
ctx.currentTime = audioContext.currentTime * 1000
// 2.匹配正确的歌词
if (!ctx.lyricInfos.length) return
let index = ctx.lyricInfos.length - 1
for (let i = 0; i < ctx.lyricInfos.length; i++) {
const info = ctx.lyricInfos[i]
if (info.time > audioContext.currentTime * 1000) {
index = i - 1
break
}
}
if (index === ctx.currentLyricIndex) return
// 3.获取歌词的索引index和文本text
// 4.改变歌词滚动页面的位置
const currentLyricText = ctx.lyricInfos[index].text
ctx.currentLyricText = currentLyricText
ctx.currentLyricIndex = index
})
歌词的处理
首先进行歌词解析,我们请求下来的歌词一般是 ’[00:58.65]他们说 要缝好你的伤 没有人爱小丑‘ 这种格式,用正则表达式解析,然后做相应处理后保存下来
// 正则(regular)表达式(expression): 字符串匹配利器
// [00:58.65]
const timeRegExp = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/
export function parseLyric(lyricString) {
const lyricStrings = lyricString.split("\n")
const lyricInfos = []
for (const lineString of lyricStrings) {
// [00:58.65]他们说 要缝好你的伤 没有人爱小丑
const timeResult = timeRegExp.exec(lineString)
if (!timeResult) continue
// 1.获取时间
const minute = timeResult[1] * 60 * 1000
const second = timeResult[2] * 1000
const millsecondTime = timeResult[3]
const millsecond = millsecondTime.length === 2 ? millsecondTime * 10: millsecondTime * 1
const time = minute + second + millsecond
// 2.获取歌词文
const text = lineString.replace(timeRegExp, "")
lyricInfos.push({ time, text })
}
return lyricInfos
}
歌词样式:歌词并不是像文本那样排列,应该保证正在播放的歌词时刻在屏幕中间,所以需要给歌词加上边距和下边距
style="padding-top: {{index === 0 ? (contentHeight/2-66) : 0}}px; padding-bottom: {{ index === lyricInfos.length - 1 ? (contentHeight/2+66) : 0 }}px;"
并且当现在播放歌词要有不同样式
class="item {{currentLyricIndex === index ? 'active': ''}}"
歌词滚动:根据时间匹配,找到大于现在时间的前一天歌词的索引即可,最后一句歌词需要特殊处理,有了索引,就能让其滚到一定距离了
if (!ctx.lyricInfos.length) return
let index = ctx.lyricInfos.length - 1
for (let i = 0; i < ctx.lyricInfos.length; i++) {
const info = ctx.lyricInfos[i]
if (info.time > audioContext.currentTime * 1000) {
index = i - 1
break
}
}
if (index === ctx.currentLyricIndex) return
this.setData({ currentLyricIndex, lyricScrollTop: currentLyricIndex * 35 })
播放模式
我们知道,播放模式有顺序播放,单曲循环和随机播放,我们有播放列表和歌曲索引,这个功能很好做
switch (ctx.playModeIndex) {
case 1:
case 0: // 顺序播放
index = isNext ? index + 1: index - 1
if (index === length) index = 0
if (index === -1) index = length - 1
break
case 2: // 随机播放
index = Math.floor(Math.random() * length)
break
}
拖动进度
进度条是可以拖到的,我们可以监听onSliderChanging,onSliderChange事件,前者只改变当前时间和进度条,需要适当节流,且当前者拖动时不受歌曲播放的影响,可用isSliderChanging变量来实现;后者则用 audioContext.seek(currentTime / 1000)改变播放进度。
//根据当前的滑块值, 计算出对应的时间
const currentTime = value / 100 * this.data.durationTime
onSliderChanging: throttle(function(event) {
// 1.获取滑动到的位置的value
const value = event.detail.value
// 2.根据当前的值, 计算出对应的时间
const currentTime = value / 100 * this.data.durationTime
this.setData({ currentTime })
// 3.当前正在滑动
this.data.isSliderChanging = true
}, 100),