小程序--音乐播放器功能开发

通过微信小程序实现一个音乐播放器功能,功能如下,实现效果图如下

  • 最终可以去github克隆,看最终效果https://github.com/MrZHLF/wx-music.git在这里插入图片描述
    • 歌词联动
    • 播放,暂停,切换上一首和下一首
    • 全局背景音乐播放
    • 歌词播放选择状态
    • 拖拽播放进度
    • 保存播放歌曲当前记录

通过分析这个页面,可以把这个页面拆分成三个组件,分别对应歌词组件,进度条组件和下面切换按钮组件

页面布局

<view class="player-container" style="background:url({{picUrl}}) center/cover no-repeat"></view>
<view class="player-mask"></view>

<view class="player-info">
  <view class="player-disc {{isPlaying ? 'play' : ''}}" bindtap="onChangeLyriShow" hidden="{{isLyricShow}}">
    <image class="player-img rotation {{isPlaying?'':'rotation-paused'}}" src="{{picUrl}}"></image>
  </view>
  <!-- 控制面板 -->
  <view class="control">
    <text class="iconfont icon-shangyishoushangyige"  bind:tap="onPrev"></text>
    <text class="iconfont {{isPlaying ? 'icon-zanting1' : 'icon-bofang1'}}" bind:tap="togglePlaying"></text>
    <text class="iconfont icon-xiayigexiayishou" bind:tap="onNext"></text>
  </view>
</view>

在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, //进度
  },
<view class="container">
  <text class="time">{{showTime.currentTime}}</text>
  <!-- 滑动 -->
  <view class="control">
    <movable-area class="movable-area">
      <movable-view direction="horizontal" class="movable-view" damping="1000" x="{{movableDis}}" bindchange='onChange' bindtouchend="onTouchEnd"></movable-view>
    </movable-area>
    <progress stroke-width="4" backgroundColor="#969696" activeColor="#fff" percent="{{progress}}"></progress>
  </view>
  <text class="time">{{showTime.totalTime}}</text>
</view>
在父组件引入使用
首先在json文件引入
{
  "usingComponents": {
    "x-progress-bar": "/components/progress-bar/progress-bar"
  }
}
然后使用
  <!-- 进度条 -->
  <view class="progress-bar">
   <x-progress-bar ></x-progress-bar>
  </view>
获取进度条的宽度

因为每个手机型号不同,所以要动态获取宽度,以便于后面的计算

  • 这个方法在已进入到页面就要调用,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
    },

歌词播放滚动

页面布局

<scroll-view hidden="{{isLyricShow}}" class="lyric-scroll" scroll-y scroll-top="{{scrollTop}}" scroll-with-animation="true">
  <view class="lyric-panel">
    <block wx:for="{{lrcList}}" wx:key="item">
      <view class="lyric {{index==nowLyricIndex?'hightlight-lyric': ''}}">{{item.lrc}}</view>
    </block>
  </view>
</scroll-view>

这个时候需要在父组件,把请求的接口数据,传递给歌词组件,当页面一进来的时候,就需要调用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<len;i++) {
        if (currentTime <= lrcList[i].time) {
            this.setData({
              nowLyricIndex:i-1,
              scrollTop: (i - 1) * lyricHeight
            })
            break
        }
      }
    },
  • 因为我们在css样式中写的rpx,但是页面渲染的是px,所以我们要计算1rpx的大小
lifetimes:{
    ready(){
      wx.getSystemInfo({
        success(res) {
          // 计算除1rpx的大下
          lyricHeight = res.screenWidth / 750 * 64
        },
      })
    }
  },
发布了76 篇原创文章 · 获赞 71 · 访问量 1829

猜你喜欢

转载自blog.csdn.net/Govern66/article/details/104622467