微信小程序实现音乐播放器(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"
}

app.wxss

page{
    
    
  height: 100%;
}

app.js

App({
    
    
  globalData:{
    
    
    isPlayGlobal:false,   //当前是否有歌曲在播放
    musicIdGlobal:''     //当前正在播放的歌曲是哪首
  }
})

pages/index/index.json

{
    
    
  "usingComponents": {
    
    },
  "navigationBarTitleText": "播放列表"
}

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>

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;
}

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),
    })
  }
})

pages/music/music.json

{
    
    
  "usingComponents": {
    
    },
  "navigationBarBackgroundColor": "#2f434e",
  "navigationBarTitleText": "音乐详情"
}

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>

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;
}

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();
    })
  }
})

相关链接

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

猜你喜欢

转载自blog.csdn.net/qzw752890913/article/details/125973413