前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >微信小程序中将图片与音乐制作成MV

微信小程序中将图片与音乐制作成MV

作者头像
越陌度阡
发布2020-11-26 14:38:43
2.1K0
发布2020-11-26 14:38:43
举报

最近一直在开发一个类似于小年糕的微信小程序,在开发制作MV功能时 ,花费了一些心思,其间主要遇到了以下一些问题点:

1. 上传图片的动画效果如何像播放视频一样实现播放与暂停?

2. 用户上传的图片数量不确定,在音乐没有播完之前,上传图片太多或太少将如何处理?

3. 如何让展现的歌词与当前播放的那一句保持同步,即唱哪一句就显示哪一句?

4. 当前音乐的播放时间如何与自定义进度条的进度保持一致?

针对以上问题,首先我们来看一下实现的效果,

下面我们来一一解答以上提出的问题点:

第一个问题,动画如何暂停与播放,我采用了animation-play-state 这个属性来控制动作的播放与暂停,当它的值为 play 时,动画会播放,如果值为 paused 时,动画会暂停。

第二个问题,当音乐还在播放时,用户上传的图片如果太少,将图片进行了循环展示,直到音乐播放完毕,由于整个MV的时长取决于所选择音乐的时长,如果上传的图片太多,当音乐播放完毕时,我将后面的图片进行了省略处理。

第三个问题,为了让歌词展示与音乐播放保持同步,我对音乐的歌词格式进行了处理,将每一句歌词与该歌词的播放时间分别组成一个对象,然后将多个对象组成一个数组,将数组循环展示在页面上,其中时间格式为是整型的秒数。在播放时,将每一句歌词的播放时间与音乐当前播放的时间进行了对比,如果歌词的播放时间大于等于当前音乐播放时间,并且小于一下句歌词的播放时间就显示该歌词,否则不显示。

第四个问题,为了让播放时间与进度条的进度同步,我利用了 onTimeUpdate 这个API,即监听音乐播放时间更新的函数,在这个API的回调函数里,获取当前音乐的动态播放时间,将播放时间与音乐的总时间相除,即可得出播放时间的百分比,然后将这个比值乘以100进行向上取整,最后将该结果赋给进度条 slider 的 value 值,这样就可以实现音乐在播的同时进度条同步更新的效果。

以上就是问题的解决方案,下面是实际的代码,在实际的代码中,我还加入了拖拽进度条快进或快退、页面显示、页面隐藏、页面销毁时对音乐播放器的一些处理。

WXML代码:

代码语言:javascript
复制
<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代码

代码语言:javascript
复制
/* 外层容器 */

.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代码

代码语言:javascript
复制
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,
                // 音乐播放时间归0
                sliderStartTime: '00:00',
                // 音乐播放时间归0
                sliderStartTimeNum: 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
            })

        }
    },

    // 暂停或播放MV
    changeMvStatus: 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
        })
    },

})
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/11/30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档