最近一直在开发一个类似于小年糕的微信小程序,在开发制作MV功能时 ,花费了一些心思,其间主要遇到了以下一些问题点:
1. 上传图片的动画效果如何像播放视频一样实现播放与暂停?
2. 用户上传的图片数量不确定,在音乐没有播完之前,上传图片太多或太少将如何处理?
3. 如何让展现的歌词与当前播放的那一句保持同步,即唱哪一句就显示哪一句?
4. 当前音乐的播放时间如何与自定义进度条的进度保持一致?
针对以上问题,首先我们来看一下实现的效果,

下面我们来一一解答以上提出的问题点:
第一个问题,动画如何暂停与播放,我采用了animation-play-state 这个属性来控制动作的播放与暂停,当它的值为 play 时,动画会播放,如果值为 paused 时,动画会暂停。
第二个问题,当音乐还在播放时,用户上传的图片如果太少,将图片进行了循环展示,直到音乐播放完毕,由于整个MV的时长取决于所选择音乐的时长,如果上传的图片太多,当音乐播放完毕时,我将后面的图片进行了省略处理。
第三个问题,为了让歌词展示与音乐播放保持同步,我对音乐的歌词格式进行了处理,将每一句歌词与该歌词的播放时间分别组成一个对象,然后将多个对象组成一个数组,将数组循环展示在页面上,其中时间格式为是整型的秒数。在播放时,将每一句歌词的播放时间与音乐当前播放的时间进行了对比,如果歌词的播放时间大于等于当前音乐播放时间,并且小于一下句歌词的播放时间就显示该歌词,否则不显示。
第四个问题,为了让播放时间与进度条的进度同步,我利用了 onTimeUpdate 这个API,即监听音乐播放时间更新的函数,在这个API的回调函数里,获取当前音乐的动态播放时间,将播放时间与音乐的总时间相除,即可得出播放时间的百分比,然后将这个比值乘以100进行向上取整,最后将该结果赋给进度条 slider 的 value 值,这样就可以实现音乐在播的同时进度条同步更新的效果。
以上就是问题的解决方案,下面是实际的代码,在实际的代码中,我还加入了拖拽进度条快进或快退、页面显示、页面隐藏、页面销毁时对音乐播放器的一些处理。
WXML代码:
<view style="height:calc(100vh - {{height}}rpx - 202rpx)" class="mv-box"><!-- MV动画 --><block><view class="mv" bindtap="showPlayControl"><image class="mv-background" src="{{mvImageSrc[mvImgIndex]}}" style="animation:{{animationArray[mvImgIndex%4]}} 5s linear 1 both;animation-play-state:{{mvStatus}};" wx:if="{{mvImageSrc.length}}"></image></view><view class="mv" bindtap="showPlayControl"><image class="mv-image" wx:for="{{mvImageSrc}}" wx:key="index" src="{{item}}" style="animation:{{animationArray[index%4]}} 5s linear 1 both;animation-play-state:{{mvStatus}};" bindanimationend='singleMvOver' wx:if="{{mvImageSrc.length && mvImgIndex==index?true:false}}"mode="widthFix"></image></view></block><!-- MV歌词 --><view class="lyric" wx:for="{{mvMusicInfo.lrcText}}" wx:key="index" wx:if="{{mvMusicInfo.lrcText.length && index < mvMusicInfo.lrcText.length && mvMusicInfo.lrcText[index][0]<= sliderStartTimeNum && sliderStartTimeNum < mvMusicInfo.lrcText[index + 1][0]}}">{{item[1]}}</view><!-- 控制条 --><view class="play-control" hidden="{{!showPlayControl}}"><view class="play-control-left" bindtap="changeMvStatus"><image src="/images/play-white-40.png" wx:if="{{mvIsPlay}}"></image><image src="/images/pause-white-40.png" wx:if="{{!mvIsPlay}}"></image></view><view class="play-control-right" bindtap="clickSlider"><text class="start-time">{{sliderStartTime}}</text><slider block-size="16" value="{{sliderValue}}" backgroundColor="#838383" activeColor='#ffffff' bindchanging="dragingEvent" bindchange="dragEndEvent"></slider><text class="end-time">{{sliderEndTime}}</text></view></view></view>
WXSS代码
/* 外层容器 */.mv-box {width: 100%;background-color: #fff;display: flex;position: relative;z-index: 25;
}/* MV动画 */.mv {width: 100%;height: 100%;position: absolute;left: 0;top: 0;display: flex;flex-direction: column;justify-content: space-around;align-items: center;
}.mv-background {filter: blur(10px);height: 100%;
}.mv-image {position: absolute;
}/* 放大动画 */@keyframes enlarge {0% {width: 750rpx;transform: scale(1);}100% {width: 750rpx;transform: scale(1.2);opacity: 0.3;}
}/* 缩小动画 */@keyframes shrink {0% {width: 750rpx;transform: scale(1.2);}100% {width: 750rpx;transform: scale(1);opacity: 0.3;}
}/* 左移动画 */@keyframes moveLeft {0% {width: 850rpx;left: 0rpx;}100% {width: 850rpx;left: -100rpx;opacity: 0.3;}
}/* 右移动画 */@keyframes moveRight {0% {width: 850rpx;left: -100rpx;}100% {width: 850rpx;left: 0;opacity: 0.3;}
}/* MV歌词 */.lyric {position: absolute;bottom: 80rpx;left: 0;width: calc(100% - 60rpx);padding: 0 30rpx;line-height: 80rpx;z-index: 30;text-align: center;font-size: 40rpx;font-weight: 600;color: #ff2066;
}/* 控制条 */.play-control {position: absolute;bottom: 0;left: 0;width: 100%;height: 80rpx;display: flex;flex-direction: row;justify-content: space-between;align-items: center;background-color: #030303;z-index: 30;
}.play-control-left {width: 80rpx;height: 80rpx;
}.play-control-left image {width: 40rpx;height: 40rpx;padding: 20rpx;
}.play-control-right {width: 670rpx;height: 80rpx;display: flex;flex-direction: row;justify-content: space-between;align-items: center;
}.play-control-right .start-time {display: inline-block;height: 40rpx;line-height: 40rpx;width: 70rpx;color: #fff;font-size: 22rpx;text-align: center;
}.play-control-right slider {width: 490rpx;
}.play-control-right .end-time {display: inline-block;height: 40rpx;line-height: 40rpx;width: 90rpx;color: #fff;font-size: 22rpx;text-align: left;
}
JS代码
Page({data: {// 系统状态栏高度// 实际动态获取height: 40,// MV轮播的图片mvImageSrc: [],// 当前轮播图片的下标mvImgIndex: 0,// MV轮播的动画效果animationArray: ['enlarge', 'shrink', 'moveLeft', 'moveRight'],// MV动画的播放状态mvStatus: 'play',// 音乐信息mvMusicInfo: {},// 显示音频播放进度条showPlayControl: true,// 控制条暂停与播放的图标mvIsPlay: true,// 音乐正在播放时间sliderStartTime: '00:00',// 音乐正在播放时间秒数sliderStartTimeNum: 0,// 当前控制条播放进度sliderValue: 0,// 音乐播放总时长sliderEndTime: '00:00',// 音乐播放总秒数sliderEndTimeNum: 0,},// 页面加载时onLoad: function(options) {// 创建音乐播放器var audioCtx = wx.createInnerAudioContext();this.setData({audioCtx: audioCtx}, function() {this.getData();})},// 获取图片和音乐信息getData: function() {// 模拟请求获取MV相关信息// MV的图片(图片地址已作处理,非真实有效)var mvImageSrc = ["https://qnybd.xxxxx.cn/44b76201910210912393964.jpg","https://qnybd.xxxxx.cn/7e33520191030155441391.png","https://qnybd.xxxxx.cn/43c96201910301553019375.png"];// 音乐信息var mvMusicInfo = {// 音乐地址(音乐地址已作处理,非真实有效)sourcePath: 'https://qnybd.xxxxx.cn/d0d6a8eacd9ec492ba0b5424335bfb4e.mp3',// 音乐歌词(由于歌词太多,此处仅做部分示例)lrcText: "[00:00.860]作词:杨丽丽\n[00: 02.580] 混音:周佳佳\n[00:04.480] 编曲:王悦悦",// 音乐时长musicTime: '04:21'};// 添加音乐地址var audioCtx = this.data.audioCtx;audioCtx.src = mvMusicInfo.sourcePath;audioCtx.play();// 播放状态,绑定播放进度更新事件,控制进度条和时间显示// onTimeUpdate在audioCtx.onTimeUpdate(this.timeUpdate);// 处理音乐歌词的格式mvMusicInfo.lrcText = this.parseLyric(mvMusicInfo.lrcText);// 播放器控件的最大时长var sliderEndTime = mvMusicInfo.musicTime;var sliderEndTimeNum = this.formatTimeToNum(sliderEndTime);// 更新页面信息this.setData({mvImageSrc: mvImageSrc,mvMusicInfo: mvMusicInfo,sliderEndTime: sliderEndTime,sliderEndTimeNum: sliderEndTimeNum,audioCtx: audioCtx})},// 滑块拖动过程中的事件dragingEvent: function(e) {var that = this;// 暂停播放that.data.audioCtx.stop();// 取消监听播放进度that.data.audioCtx.offTimeUpdate();that.setData({mvIsPlay: false,mvStatus: 'paused',});},// 拖动完成后dragEndEvent: function(e) {var that = this;// 总时间var sliderEndTimeNum = that.data.sliderEndTimeNum;// 拖动百分比var percent = e.detail.value / 100;// 计算拖拽时的播放时间var sliderStartTimeNum = Math.ceil(sliderEndTimeNum * percent);var sliderStartTime = that.formatTimeToStr(sliderStartTimeNum);that.setData({mvIsPlay: true,mvStatus: 'play',sliderStartTimeNum: sliderStartTimeNum,sliderStartTime: sliderStartTime,sliderValue: e.detail.value},function(){// 跳转时间that.data.audioCtx.seek(that.data.sliderStartTimeNum);// 播放音乐that.data.audioCtx.play();// 监听进度that.data.audioCtx.onTimeUpdate(that.timeUpdate);});},// 处理歌词格式parseLyric: function(text) {// 按换行切割var text = text.replace(/\\n/g,'\n');text = text.replace(/\n/g, '===');var lines = text.split('===');// 筛选歌词,修正时间let list = [];for (let b = 0; b < lines.length; b++) {let item = lines[b];// 拆分与切割item = item.replace(/\[/g, '');item = item.split(']');// 去掉有时间的空歌词if (item[item.length - 1]) {for (let j = 0; j < item.length - 1; j++) {// 去掉时间的毫秒数let index = item[j].indexOf('.');// 截取分和秒并替换分后面的空格item[j] = item[j].substring(0, index).replace(/\s+/g, '');}list.push(item);};};// 处理歌词多个时间共享的问题let arr = [];for (let k = 0; k < list.length; k++) {if (list[k].length == 2) {list[k][0] = App.formatTimeToNum(list[k][0]);arr.push(list[k]);} else {for (let n = 0; n < (list[k].length - 1); n++) {let obj = [// 时间App.formatTimeToNum(list[k][n]),// 共用的歌词list[k][list[k].length - 1],];arr.push(obj);}}};// 对歌词按时间排序arr.sort(function (a, b) {return a[0] - b[0];});return arr;},// 页面影藏onHide: function() {this.setData({mvIsPlay: false,mvStatus: 'paused'});this.data.audioCtx.pause();},// 页面显示onShow: function() {this.setData({mvIsPlay: true,mvStatus: 'play'});if (this.data.audioCtx) {this.data.audioCtx.play();};},// 页面卸载onUnload: function() {this.data.audioCtx.destroy();},// 播放的时候,更新进度条和时间显示timeUpdate: function() {let that = this;// 当前播放的秒数let sliderStartTimeNum = Math.ceil(that.data.audioCtx.currentTime);let sliderValue = Math.round(sliderStartTimeNum / that.data.audioCtx.duration * 100);if (sliderValue == 100) {that.data.audioCtx.pause();that.setData({// 切换为暂停按钮mvIsPlay: false,// 停止动画mvStatus: 'paused',// 图片换为第一张mvImgIndex: 0,// 音乐播放时间归0sliderStartTime: '00:00',// 音乐播放时间归0sliderStartTimeNum: 0,// 当前播放进度sliderValue: 0,});} else {that.setData({sliderStartTime: that.formatTimeToStr(sliderStartTimeNum),sliderStartTimeNum: sliderStartTimeNum,sliderValue: sliderValue})}},// 将时间秒数转为字符串formatTimeToStr: function(num) {var a = Math.floor(num / 600);var b = Math.floor(num % 600 / 60);var c = Math.floor((num - a * 600 - b * 60) / 10);var d = parseInt((num - a * 600 - b * 60) % 10);var timeStr = '' + a + b + ':' + c + d;return timeStr},// 将时间格式转为秒数formatTimeToNum: function(str) {var first = str.substring(0, 2);var end = str.substring(3, 5);var allTimeStr = first + end;var allTimeArry = allTimeStr.split('');var allTimeNum = 0;for (var i = 0; i < allTimeArry.length; i++) {if (i == 0) {allTimeNum += parseInt(allTimeArry[i] * 600);} else if (i == 1) {allTimeNum += parseInt(allTimeArry[i] * 60);} else if (i == 2) {allTimeNum += parseInt(allTimeArry[i] * 10);} else if (i == 3) {allTimeNum += parseInt(allTimeArry[i]);}};return allTimeNum},// 显示播放进度条showPlayControl: function(e) {this.setData({showPlayControl: true})},// 单个图片播放结束时singleMvOver: function() {var that = this;var mvImgIndex = that.data.mvImgIndex + 1;if (mvImgIndex == that.data.mvImageSrc.length) {that.setData({mvImgIndex: 0});} else {that.setData({mvImgIndex: mvImgIndex})}},// 暂停或播放MVchangeMvStatus: function() {var that = this;// 获取当前正在播放的状态var isPlay = that.data.mvIsPlay;if (isPlay) {// 暂停音乐that.data.audioCtx.pause();this.setData({mvIsPlay: false,mvStatus: 'paused',});} else {// 播放that.data.audioCtx.play();this.setData({mvIsPlay: true,mvStatus: 'play',});};},// 点击滑动滑块clickSlider: function(e) {this.setData({showPlayControl: false})},})

















