• 微信小程序实现音乐播放器(5)


    前情提要

    pubsubjs实现页面通信

    1. npm install --save pubsub-js,安装第三方库pubsub-js。
    2. import PubSub from "pubsub-js",引入第三方库pubsub。
    3. 订阅消息:id = PubSub.subscribe(消息名,callback),且callback的第一个形参默认是消息名,后面则是开发者自己传入的参数。
    4. 发布消息:PubSub.publish(消息名,开发者的参数)
    5. 取消订阅:PubSub.unsubscribe(id)

    moment.js实现时间格式化

    本例会使用第三方库moment.js来格式化时间。

    1. 安装依赖。npm install --save moment
    2. 微信开发者工具中,工具>构建npm
    3. 页面中引入并使用moment。
      import moment from "moment";
      moment(时间).format(格式);

    BackgroundAudioManager

    BackgroundAudioManager,是全局唯一的背景音频管理器。小程序切入后台,如果音频处于播放状态,可以继续播放。但是后台状态不能通过调用API操纵音频的播放状态。
    从微信客户端6.7.2版本开始,如果需要小程序切后台后继续播放音频,需要在全局配置文件 app.json 中配置 requiredBackgroundModes 属性,如:"requiredBackgroundModes": ["audio"]

    BackgroundAudioManager实例,可以通过 wx.getBackgroundAudioManager() 获取。BackgroundAudioManager实例具有如下属性和方法:

    1. title,音频标题,必填
    2. src,音频的数据源,默认值是空字符串,非必填。但是,只有设置了src时,音频才会自动播放
    3. duration,当前音频的长度,number类型,单位为s,即秒。
    4. currentTime,当前音频的播放位置,number类型,单位为s,即秒。
    5. play(),播放音乐。
    6. pause(),暂停音乐。
    7. onPlay(function callback),监听背景音频播放事件。
    8. onPause(function callback),监听背景音频暂停事件。
    9. onStop(function callback),监听背景音频停止事件。
    10. onEnded(function callback),监听背景音频自然播放结束事件。
    11. onTimeUpdate(function callback),监听背景音频播放进度更新事件。

    小程序项目

    代码涉及的主要文件有:

    1. app.json
    2. app.wxss
    3. app.js
    4. pages/index/index.json
    5. pages/index/index.wxml
    6. pages/index/index.wxss
    7. pages/index/index.js
    8. pages/music/music.json
    9. pages/music/music.wxml
    10. pages/music/music.wxss
    11. pages/music/music.js

    在这里插入图片描述

    app.json

    {
      "pages": [
        "pages/index/index",
        "pages/music/music"
      ],
      "window": {
        "navigationBarBackgroundColor": "#624d2e",
        "navigationBarTitleText": "首页",
        "navigationBarTextStyle": "white"
      },
      "requiredBackgroundModes": [
        "audio"
      ],
      "style": "v2",
      "sitemapLocation": "sitemap.json"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    app.wxss

    page{
      height: 100%;
    }
    
    • 1
    • 2
    • 3

    app.js

    App({
      globalData:{
        isPlayGlobal:false,   //当前是否有歌曲在播放
        musicIdGlobal:''     //当前正在播放的歌曲是哪首
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    pages/index/index.json

    {
      "usingComponents": {},
      "navigationBarTitleText": "播放列表"
    }
    
    • 1
    • 2
    • 3
    • 4

    pages/index/index.wxml

    <view class="index-container">
      <view class="header">
        <image src="/static/images/icon-play-square.png">image>
        <text>播放全部text>
        <text>{{musicList.length}}text>
      view>
      <scroll-view class="music-list" enable-flex scroll-y >
        <view class="music-item" wx:for="{{musicList}}" wx:key="id" bindtap="handleTap" data-musicindex="{{index}}" data-musicitem="{{item}}">
          <view class="music-index">{{index+1}}view>
          <image class="music-image" src="{{item.picUrl}}">image>
          <view class="music-info">
            <view class="musci-name">{{item.name}}view>
            <view class="music-author">{{item.author}}view>
          view>
          <image class="btn-more" src="/static/images/icon-more.png">image>
        view>
      scroll-view>
    view>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    pages/index/index.wxss

    .index-container{
      padding: 20rpx;
      background:#624d2e
    }
    .header{
      display: flex;
      align-items: center;
    }
    .header image{
      width: 28rpx;
      height: 28rpx;
      margin-right: 20rpx;
    }
    .header text{
      color: #fff;
      height: 28rpx;
      line-height: 28rpx;
      margin-right: 20rpx;
      font-size: 28rpx;
    }
    .music-list{
      margin-top: 20rpx;
      color: #fff;
      height: calc(100vh - 88rpx);
    }
    .music-item{
      display: flex;
      align-items: center;
      height: 100rpx;
      margin: 20rpx 0;
      position: relative;
    }
    .music-item .music-index{
      width: 50rpx;
      font-size: 28rpx;
      color: #aaa;
    }
    .music-item .music-image{
      width: 80rpx;
      height: 80rpx;
      border-radius: 6rpx;
      margin-right: 20rpx;
    }
    .music-item .music-info{
      display: flex;
      flex-direction: column;
    }
    .music-item .music-info .music-author{
      font-size: 24rpx;
      color: #aaa;
      margin-top: 10rpx;
    }
    .music-item .btn-more{
      width: 36rpx;
      height: 36rpx;
      position: absolute;
      right: 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    pages/index/index.js

    import PubSub from "pubsub-js";
    const host = "http://localhost:3000";
    
    Page({
      data:{
        musicList:[], //歌曲列表
        musicindex:0  //进入某首歌曲后,记录该歌曲在列表中的索引
      },
      onLoad(){
        this.getDataFromServer();
    
        //接收消息,接收来自页面pages/music/music的消息,根据“上一首”or“下一首”,确定当前应该显示哪首歌曲
        PubSub.subscribe("switchsong",(msgName,type) => {
          // console.log(msgName,type);
          let {musicindex,musicList} = this.data;
          if(type === "prev"){
            if(musicindex===0) {
              musicindex = musicList.length-1;
            }else{
              musicindex--;
            }
          }else if(type === "next"){
            if(musicindex === musicList.length-1){
              musicindex = 0;
            }else{
              musicindex++;
            }
          }
          this.setData({musicindex});
          const music = musicList[musicindex];
          //发送消息,告诉页面pages/music/music,告诉它切换完成后的歌曲的详细消息。
          PubSub.publish("refreshmusic",music);
        })
      },
      getDataFromServer(){
        const result = [
          {id:"001",name:"滂沱大雨里","author":"李若溪","picUrl":host+"/images/滂沱大雨里.jpg","url":host+"/audios/滂沱大雨里.mp3",duration:161000},
          {id:"002",name:"Last Dance","author":"伍佰","picUrl":host+"/images/Last Dance.jpg","url":host+"/audios/Last Dance.mp3",duration:271000},
          {id:"003",name:"国王与乞丐","author":"华晨宇","picUrl":host+"/images/国王与乞丐.jpg","url":host+"/audios/国王与乞丐.mp3",duration:178000},
          {id:"004",name:"奇洛李维斯回信","author":"薛凯琪","picUrl":host+"/images/奇洛李维斯回信.jpg","url":host+"/audios/奇洛李维斯回信.mp3",duration:243000},
          {id:"005",name:"红日","author":"李克勤","picUrl":host+"/images/红日.jpg","url":host+"/audios/红日.mp3",duration:291000},
          {id:"006",name:"快乐崇拜","author":"潘玮柏 张韶涵","picUrl":host+"/images/快乐崇拜.jpg","url":host+"/audios/快乐崇拜.mp3",duration:329000},
          {id:"007",name:"门没锁","author":"黄品冠","picUrl":host+"/images/门没锁.jpg","url":host+"/audios/门没锁.mp3",duration:233000},
          {id:"008",name:"就是爱你","author":"陶喆","picUrl":host+"/images/就是爱你.jpg","url":host+"/audios/就是爱你.mp3",duration:340000},
          {id:"009",name:"快乐的小青蛙","author":"刘士鸣","picUrl":host+"/images/快乐的小青蛙.jpg","url":host+"/audios/快乐的小青蛙.mp3",duration:130000},
          {id:"0010",name:"友情岁月","author":"陈小春 郑伊健 谢天华 林晓峰 钱嘉乐","picUrl":host+"/images/友情岁月.jpg","url":host+"/audios/友情岁月.mp3",duration:209000},
          {id:"0011",name:"温柔","author":"五月天","picUrl":host+"/images/温柔.jpg","url":host+"/audios/温柔.mp3",duration:269000}
        ];
        this.setData({musicList:result});
      },
      handleTap(event){
        const {musicitem,musicindex} = event.currentTarget.dataset;
        this.setData({musicindex})
        wx.navigateTo({
          url: '/pages/music/music?musicitem='+JSON.stringify(musicitem),
        })
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    pages/music/music.json

    {
      "usingComponents": {},
      "navigationBarBackgroundColor": "#2f434e",
      "navigationBarTitleText": "音乐详情"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    pages/music/music.wxml

    <view class="music-container">
      <view class="music-name">{{music.name}}view>
      <view class="music-author">{{music.author}}view>
      <image class="arm {{isPlay&&'arm-reset'}}" src="/static/images/arm.png">image>
      <view class="disc-container {{isPlay&&'disc-animate'}}">
        <image class="disc" src="/static/images/disc.png">image>
        <image class="music-image" src="{{music.picUrl}}">image>
      view>
    
      <view class="progress-container">
        <text class="current-time">{{fmtCurrentTime}}text>
        <view class="bar-box">
          <view class="current-progress" style="width:{{currentWidth}}rpx">
            <view class="progress-circle">view>
          view>
        view>
        <text class="duration">{{fmtDuration}}text>
      view>
    
      <view class="player">
        <view class="btns">
          <image class="loop-btn" src="/static/images/loop.png">image>
          <image class="prev-btn" src="/static/images/prev.png" id="prev" bindtap="handleSwitch">image>
          <image class="play-btn" src="{{isPlay?'/static/images/stop.png':'/static/images/play.png'}}" bindtap="handlePlay">image>
          <image class="next-btn" src="/static/images/next.png" id="next" bindtap="handleSwitch">image>
          <image class="list-btn" src="/static/images/list.png">image>
        view>
      view>
    view>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    pages/music/music.wxss

    .music-container{
      height: 100%;
      display: flex;
      flex-direction: column;
      align-items: center;
      background: #2f434e;
      position: relative;
    }
    .music-container .music-name{
      margin: 10rpx 0;
      color: #fff;
      font-size: 36rpx;
    }
    .music-container .music-author{
      color: #bbb;
      font-size: 28rpx;
      margin: 6rpx 0;
    }
    .music-container .arm{
      width:204rpx;
      height: 358rpx;
      position: relative;
      left: 72rpx;
      z-index: 99;
      transform: rotate(-15deg);
      transform-origin: 30rpx 30rpx;
      transition: transform .7s linear;
    }
    .disc-container{
      position: relative;
      top: -128rpx;
      width: 490rpx;
      height: 490rpx;
    }
    .disc-container .disc{
      width: 100%;
      height: 100%;
    }
    .disc-container .music-image{
      width: 270rpx;
      height: 270rpx;
      border-radius: 100%;
      position: absolute;
      left: 0;right: 0;top: 0;bottom: 0;
      margin: auto;
    }
    .music-container .arm-reset{
      transform: rotate(0deg);
    }
    .disc-animate{
      animation: rotate 2.5s 1s linear infinite;
    }
    @keyframes rotate{
      from{
        transform: rotate(0deg);
      }
      to{
        transform: rotate(360deg);
      }
    }
    .player{
      width: 100%;
      position: absolute;
      bottom: 60rpx;
    }
    .btns{
      display: flex;
      align-items: center;
      justify-content: space-evenly;
    }
    .btns image{
      width: 36rpx;
      height: 36rpx;
    }
    .btns .play-btn,.btns .stop-btn{
      width: 90rpx;
      height: 90rpx;
    }
    .progress-container {
      width: 100%;
      display: flex;
      justify-content: space-around;
      align-items: center;
    }
    .progress-container .bar-box{
      width: 456rpx;
      height: 4rpx;
      line-height: 4rpx;
      background: #888;
    }
    .bar-box .current-progress{
      position: relative;
      height: 4rpx;
      line-height: 4rpx;
      background: #fff;
    }
    .progress-circle{
      width: 16rpx;
      height: 16rpx;
      border-radius: 50%;
      background: #fff;
      position: absolute;
      right: -16rpx;
      top: -6rpx;
    }
    .progress-container text{
      color: #ccc;
      font-size: 22rpx;
      margin: 0 40rpx;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110

    pages/music/music.js

    import PubSub from "pubsub-js";
    import moment from 'moment';
    const appInstance = getApp();
    
    Page({
      data:{
        isPlay:false,
        music:{},
        fmtDuration:"00:00",
        fmtCurrentTime:"00:00",
        currentWidth:0
      },
      onLoad(options){
        const music = JSON.parse(options.musicitem);
        let fmtDuration = moment(music.duration).format("mm:ss");
        this.setData({music,fmtDuration})
    
        const {isPlayGlobal,musicIdGlobal} = appInstance.globalData;
        const {id} = this.data.music;
        if(isPlayGlobal && musicIdGlobal === id) {
          this.setData({isPlay:true});
        }
      
        this.bam = wx.getBackgroundAudioManager();
        this.bam.onPlay(() => {
          this.setData({isPlay:true})
          appInstance.globalData.isPlayGlobal = true;
          appInstance.globalData.musicIdGlobal = this.data.music.id;
        })
        this.bam.onPause(() => {
          this.setData({isPlay:false})
          appInstance.globalData.isPlayGlobal = false;
        })
        this.bam.onStop(() => {
          this.setData({isPlay:false})
          appInstance.globalData.isPlayGlobal = false;
        })
        this.bam.onEnded(() => {
          // this.setData({isPlay:false});
          // appInstance.globalData.isPlayGlobal = false;
    
          // 音乐自然播放结束,则切换至下一首
          PubSub.publish("switchsong","next");
          //接收消息,接收来自pages/index/index的消息,显示切换后的歌曲详情
          const eventId = PubSub.subscribe("refreshmusic",(msgName,music) => {
            PubSub.unsubscribe(eventId);
            this.setData({music})
            this.musicControl();
    
            const fmtDuration = moment(this.data.music.duration).format("mm:ss");
            this.setData({fmtDuration,fmtCurrentTime:"00:00"})
          })  
        })
        this.bam.onTimeUpdate(() => {
          const currentWidth = Math.floor(456 * this.bam.currentTime / this.bam.duration);
          const fmtCurrentTime = moment(this.bam.currentTime * 1000).format("mm:ss");
          this.setData({currentWidth,fmtCurrentTime});
        })
      },
      handlePlay(){
        const isPlay = !this.data.isPlay;
        this.setData({isPlay});
        this.musicControl();
      },
      musicControl(){
        const {isPlay} = this.data;
        if(isPlay){
          this.bam.src = this.data.music.url;
          this.bam.title = this.data.music.name;
        }else{
          this.bam.pause();
        }
      },
      handleSwitch(event){
        const type = event.target.id;
        //发送消息,告诉pages/index/index:切上一首还是下一首。prev代表切上一首,next代表切下一首。
        PubSub.publish("switchsong",type);
    
        //接收消息,接收来自pages/index/index的消息,显示切换后的歌曲详情
        const eventId = PubSub.subscribe("refreshmusic",(msgName,music) => {
          PubSub.unsubscribe(eventId);
          this.setData({music})
          this.musicControl();
        })
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    相关链接

    moment.js中文文档
    pubsubjs
    微信小程序实现音乐播放器(4)(使用pubsubjs实现页面间通信)

  • 相关阅读:
    deeplab实现自己遥感地质分割数据集
    嵌入式&C++转java 刷题day2
    前端_Vue_1.初识Vue
    PHREEQC建模及典型案例解析与高阶拓展应用【反向“编译”、“玩转”后处理技术、GibbsStudio和PhreePlo方法】
    WSUS 修补程序管理的替代方法
    LeetCode 面试题 10.05. 稀疏数组搜索
    高项 范围管理论文
    fiddler抓包番外————了解工具栏
    CentOS 7 右上角网络连接图标消失,设置网络有线消失解决办法
    2022前端面试题上岸手册-浏览器部分
  • 原文地址:https://blog.csdn.net/qzw752890913/article/details/125973413