小程序音乐播放器
在哈瓦纳街头俱乐部小程序中,做过音乐播放器感觉不错,记录一下,项目已经是线上的,大家可以到小程序中搜索 哈瓦纳街头俱乐部,
在这里可以看到实际效果
首先就是说的歌词部分,歌词的时间轴,下图这种格式的歌词,定好歌词的时间轴
然后下边就是wxml,js,wxss,注释代码上有,我就不做解释了,直接上代码
<!--homeSub/diy/diy.wxml--> <import src="/pages/temp/temp.wxml" /> <view class="article {{appData.screenProp}} flexcc"> <template is="header" data="{{appData,hideBack}}" /> <image class="bg" src="{{appData.imgCDN}}images/music/bg.jpg" mode="widthFix"></image> <view class="content"> <swiper class="swiper" bindchange="bindchange"> <swiper-item class="song"> <div class="topBanner"> <!-- banner --> <!-- <swiper class="topSwiper" indicator-dots="true" indicator-color="rgba(255,255,255,0.4)" indicator-active-color="rgba(255,184,14,1)"> <swiper-item wx:for="{{banner}}" wx:key="*this" style="overflow: visible;"> --> <image class="banner" src="{{appData.imgCDN}}images/music/banner.png{{appData.mathNum}}"></image> <!-- </swiper-item> </swiper> --> <view class="dotss"> <view class="dot dot1"></view> <view class="dot dot2"></view> </view> <view class="title"> <image class="songName" src="{{appData.imgCDN}}images/music/title.png"></image> <image class="name" src="{{appData.imgCDN}}images/music/name.png{{appData.mathNum}}"></image> </view> </div> </swiper-item> <!-- 歌词 --> <swiper-item class="lyric"> <scroll-view class="musicCode" scroll-y enhanced="{{true}}" bounces="{{false}}" show-scrollbar="{{false}}" scroll-top="{{-updistance}}rpx" scroll-with-animation="{{true}}" binddragstart="catchtouchstart" binddragging="binddragging" binddragend="catchtouchend"> <view class="datalist"> <view class="music_code {{opacity}} {{activeIndex == index ? 'cTime' : ''}}" wx:for="{{datalist}}" wx:key="*this">{{item.text}}</view> </view> </scroll-view> </swiper-item> </swiper> <!-- 进度条 --> <view class="musicMenu"> <view class="currentTime">{{currentTime}}</view> <view class="process"> <slider value="{{slideTime}}" block-size="14" max="{{processWidth}}" block-color="#fff" activeColor="#ffb80e" backgroundColor="#98a4a5" bindchanging="moveProcess1" bindchange="moveChange" /> </view> <view class="allTime">{{allTime}}</view> </view> <!-- 重新播放,播放暂停,返回 按钮 --> <view class="btnBox"> <view class="again" catchtap="again" hover-class="noPointer" hover-stay-time="1000"><image class="again_btn" src="{{appData.imgCDN}}images/music/again_btn.png"></image></view> <view class="play" catchtap="play" hover-class="noPointer" hover-stay-time="1000"> <image class="play_btn" src="{{appData.imgCDN}}images/music/play_btn.png" hidden="{{isPlay}}"></image> <image class="pause_btn" src="{{appData.imgCDN}}images/music/pause_btn.png" hidden="{{!isPlay}}"></image> </view> <view class="back" catchtap="goBack1" hover-class="noPointer" hover-stay-time="1000"><image class="back_btn" src="{{appData.imgCDN}}images/music/back_btn.png"></image></view> </view> </view> <!-- TODO --> </view>
const app = getApp(); const { API, beats, icom, mtj, regeneratorRuntime, promisify } = app;var Flag = true; //控制歌词滚动 //-------------------------------------------------------初始化------------------------------------------------------- let $query; Page({ data: { appData:app.data, bgmPlay: false, hideBack:false, currentTime: "00:00", //当前播放时间 processWidth: 244, // 控制进度条最大长度 slideTime: 0, // 控制进度条的长度 allTime:'', //音乐总时长 activeIndex: 0, // 控制歌词滚动的位置 updistance: 0, // 控制歌词容器滚动的距离 isPlay:false, //播放按钮状态 // banner:[ // {src:"images/music/banner1.png"}, // {src:"images/music/banner1.png"}, // ], datalist: [], //歌词 musicTime:[], //时间轴数组 time:0, }, //页面的初始数据 async onLoad(option) { wx.showLoading({mask:true}) $query = option; console.log('getQueryString', option); await app.initApp(); this.setData({appData:app.data}); app.tdsdk("arrive_song","",{ bloc_id: 'page0000028', bloc_desc_ch: '到达歌曲页', action:'page' }); wx.hideLoading() }, // 获取歌词 getSong(){ let {allTime} = this.data; let that = this; that.audioCtx.onCanplay(() => { // console.log("监听播放状态") that.audioCtx.duration; setTimeout(() => { allTime = that.s_to_hs(that.audioCtx.duration); that.setData({ allTime }) console.log(allTime,"============allTime") }, 1000) }) // 获取歌词 wx.request({ url: app.data.imgCDN + "music/havana_new.txt", data: {}, header: { 'content-type': 'application/x-www-form-urlencoded;' // 默认值 }, success(obj) { // console.log(obj) var result = obj.data.split("\n").map(r => { var arr = r.trim().substr(1).split("]"); return { time: arr[0], text: arr[1] } }) var musicTime = []; for (var i of result) { that.makeDurationToSeconds(i.time) musicTime.push(that.makeDurationToSeconds(i.time)); } // console.log(musicTime) that.setData({ datalist:result, musicTime }) // 监听音乐暂停 that.audioCtx.onPause(function(){ // console.log("音乐暂停") that.setData({ isPlay:false, time:that.audioCtx.currentTime }) }) // 监听音乐播放 that.audioCtx.onPlay(function(){ // console.log("音乐播放") that.setData({ isPlay:true, }) Flag = true; }) // 监听音乐播放 that.audioCtx.onTimeUpdate(function () { let { activeIndex, updistance, datalist, musicTime, slideTime, currentTime } = that.data currentTime = that.s_to_hs(that.audioCtx.currentTime); // console.log(that.audioCtx.currentTime) slideTime = that.audioCtx.currentTime / that.audioCtx.duration * that.data.processWidth that.setData({ currentTime, slideTime }) if(Flag){ for (var i = activeIndex; i < datalist.length; i++) { if (datalist[i].time === currentTime) { if (activeIndex != i) { updistance = i * (-50); that.setData({ activeIndex: i, updistance }) } } } } }) // 监听 音乐自然播放至结束 that.audioCtx.onEnded(function(){ that.setData({ activeIndex: 0, // 控制歌词滚动的位置 updistance: 0, // 控制歌词容器滚动的距离 slideTime: 0, // 控制进度条的长度 currentTime:"00:00",//当前播放时间 isPlay:false }) }) that.audioCtx.play(); that.setData({ isPlay:true }) } }) }, // 重新播放 again(){ let that = this; that.audioCtx.pause(); setTimeout(()=>{ that.setData({ activeIndex: 0, // 控制歌词滚动的位置 updistance: 0, // 控制歌词容器滚动的距离 slideTime: 0, // 控制进度条的长度 currentTime:"00:00",//当前播放时间 isPlay:true }) that.audioCtx.seek(0); that.audioCtx.play(); },500) // console.log(that.data.slideTime,"=====slideTime") // console.log(that.data.currentTime,"=====currentTime") // console.log(that.data.updistance,"=====updistance") }, // 开始播放 play(){ // this.setData({ // updistance:500 // }) if(this.data.isPlay){ this.audioCtx.pause(); this.setData({ isPlay:false }) }else{ this.audioCtx.play(); this.setData({ isPlay:true }) } }, bindchange(){ console.log(Flag,"=================Flag"); if(this.data.isPlay){ Flag = true; } }, // 拖动进度条过程触发的事件 moveProcess1(e) { let { value } = e.detail; let { processWidth, } = this.data; if (value >= processWidth) { value = processWidth; } this.audioCtx.pause(); }, // 拖动进度条 moveChange(e) { let { value } = e.detail; let { processWidth, activeIndex, musicTime } = this.data; let time = value * this.audioCtx.duration / processWidth; let stime = this.s_to_hs(time); var seconds = this.makeDurationToSeconds(stime) this.setData({ isPlay:true }) var findNum = this.findCloseNum(musicTime, seconds); var updistance1; for (var i = 0; i < musicTime.length; i++) { if (findNum === musicTime[i]) { updistance1 = (i - 1) * (-50); this.setData({ activeIndex: i, updistance:updistance1 }) } } this.audioCtx.seek(time); this.audioCtx.play(); }, // 当手指按下歌词区域时 catchtouchstart(){ // console.log("暂停自动滚动") // Flag = false; }, // 滚动事件 binddragging(){ Flag = false }, // 滑动歌词结束 catchtouchend(){ // console.log("开始自动滚动") let that = this; setTimeout(()=>{ Flag = true },2000) }, // 返回 goBack1() { wx.navigateBack() }, onReady: function() { }, //监听页面初次渲染完成 onShow: function() { if(this.audioCtx){ this.audioCtx.seek(this.data.time); this.audioCtx.play(); this.setData({ isPlay:true }) }else{ this.audioCtx = wx.createInnerAudioContext(); this.audioCtx.src = app.data.imgCDN + "music/havana_new.mp3"; this.audioCtx.useWebAudioImplement = true; this.getSong(); wx.setInnerAudioOption({ obeyMuteSwitch:false }) } }, //监听页面显示 onHide: function() { }, //监听页面隐藏 onUnload: function() { // console.log(this.audioCtx.duration) this.audioCtx.pause(); this.setData({ isPlay:false }) // this.audioCtx.destroy(); }, //监听页面卸载 onPullDownRefresh: function() {}, //页面相关事件处理函数--监听用户下拉动作 onReachBottom: function() {}, //页面上拉触底事件的处理函数 onShareAppMessage: function() { //用户点击右上角分享 return app.setShareData(); }, // 秒转换成分 s_to_hs(value) { let result = parseInt(value) let h = Math.floor(result / 3600) < 10 ? '0' + Math.floor(result / 3600) : Math.floor(result / 3600) let m = Math.floor((result / 60 % 60)) < 10 ? '0' + Math.floor((result / 60 % 60)) : Math.floor((result / 60 % 60)) let s = Math.floor((result % 60)) < 10 ? '0' + Math.floor((result % 60)) : Math.floor((result % 60)) result = `${m}:${s}` return result }, // 百分比 getPercent(num, total) { num = parseFloat(num); total = parseFloat(total); if (isNaN(num) || isNaN(total)) { return "-"; } return total <= 0 ? "0%" : (Math.round(num / total * 10000) / 100.00) + "%"; }, // js将格式为00:00:00的时间转换成秒钟 makeDurationToSeconds(time) { var str = time; var arr = str.split(':'); // var hs = parseInt(arr[0] * 3600); var ms = parseInt(arr[0] * 60); var ss = parseInt(arr[1]); var seconds = ms + ss; return seconds; }, // js数组中查找与目标函数最相近的数值 findCloseNum(arr, num) { var index = 0; // 保存最接近数值在数组中的索引 var d_value = Number.MAX_VALUE; // 保存差值绝对值,默认为最大数值 for (var i = 0; i < arr.length; i++) { var new_d_value = Math.abs(arr[i] - num); // 新差值 if (new_d_value <= d_value) { // 如果新差值绝对值小于等于旧差值绝对值,保存新差值绝对值和索引 if (new_d_value === d_value && arr[i] < arr[index]) { // 如果数组中两个数值跟目标数值差值一样,取大 continue; } index = i; d_value = new_d_value; } } return arr[index] // 返回最接近的数值 }, }) //end page //-------------------------------------------------------业务逻辑-------------------------------------------------------
/* homeSub/diy/diy.wxss */ .article{background-color: black;} .bg{width: 100%;} .content{width: 100%;height: 100%;position: absolute;top:0;} .content .swiper{width: 100%;height: 1185rpx;} .content .swiper .song {overflow: visible;} .content .swiper .song .topBanner{width: 530rpx;height:551rpx;} /* .swiper .song .topBanner{width: 100%;height: 620rpx;margin-top: 275rpx;} */ .content .swiper .song .banner{width: 530rpx;height: 551rpx;display: block; margin: 275rpx auto 0;} .swiper .song .topBanner .dotss{width: 65rpx;height: 30rpx;margin: 15rpx auto 0;display: flex;justify-content: space-around;} .swiper .song .topBanner .dotss .dot{width: 15rpx;height: 15rpx;border-radius: 50%;background: rgba(255,255,255,0.4);} .swiper .song .topBanner .dotss .dot1{background: rgba(255,184,14,1);} .swiper .song .topBanner .title{width: 100%;height: 100rpx;margin-top: 70rpx;} .swiper .song .topBanner .songName{width: 280rpx;height: 45rpx;display: block;margin: auto;} .swiper .song .topBanner .name{width: 132rpx;height: 25rpx;display: block;margin: 24rpx auto 0;} .content .musicMenu { width: 680rpx; height: 20rpx; margin: 80rpx auto 0; display: flex; align-items: center; justify-content: space-around; } .content .musicMenu .currentTime { font-size: 22rpx; color: #fff; white-space: nowrap; } .content .musicMenu .process { width: 560rpx; } .content .musicMenu .allTime { font-size: 22rpx; color: #fff; white-space: nowrap; } .btnBox{width: 412rpx;height: 127rpx;margin: 44rpx auto 0;display: flex;line-height: 127rpx;justify-content: space-between;} .btnBox image{width: 100%;height: 100%;} .btnBox .again{width: 77rpx;height: 78rpx;margin-top: 25rpx;} .btnBox .play{width: 127rpx;height: 127rpx;} .btnBox .back{width: 79rpx;height: 78rpx;margin-top: 25rpx;} .lyric{width: 100%;height: 886rpx;margin: auto;} .musicCode{width: 95%;height: 886rpx;text-align: center;color: white;margin: 300rpx auto 0;-webkit-mask-image: linear-gradient(to bottom,rgba(255,255,255,0) 0,rgba(255,255,255,.6) 15%,rgba(255,255,255,1) 25%,rgba(255,255,255,1) 75%,rgba(255,255,255,.6) 85%,rgba(255,255,255,0) 100%);} .musicCode .music_code{height:50rpx; line-height: 50rpx;font-size: 30rpx;letter-spacing: 1rpx;transform: scale(0.9);} .content .cTime { transform: scale(1.1); color: #ffb80e; } .datalist{padding: 300rpx 0;} .datalist{transform: translate3d(0);will-change: transform;transition: all 1s;} .content{transform: scale(.9);transform-origin: top;} .screen189 .content .musicMenu{margin: 86rpx auto 0;} .screen189 .btnBox{margin: 55rpx auto 0;} .screen189 .content{transform: scale(1);} .screen159 .content{transform: scale(.9);}
滑动歌词手指松开两秒后歌词自动滚动到正在播放的位置,感觉比较复杂的还是定位歌词这一部分,路过的大家觉得有的地方可以优化的,欢迎评论