小程序网易云(五)

一,播放歌曲模块(播放状态显示bug,跨页面通信)

bug-1解决
问题:歌曲播放功能已经实现,但是假设A歌正在播放,退出该页面,再次进入,背景音频(A歌)还在播放,但是页面的播放状态被初始化为false
需求:当上一首歌处于播放状态时,当用户再次进入歌曲详情页(song),如果当前歌曲与上次播放歌曲相同,页面播放状态(C3效果)也为播放状态
拆分需求:
    1)如何知道上一首歌是哪一首
        缓存了上一首歌的ID
    2)如何知道上一首歌的播放状态
        缓存了上一首歌的播放状态
    3)如何知道用户进入详情页
        通过生命周期函数onLoad得知
    4)如何判断当前歌曲与上一首歌相同
        将当前页面的songId与之前缓存的musicId进行对比
    5)如何让页面状态进入播放状态
        通过isplaying来区分页面究竟是播放状态还是暂停状态
        this.setData({isplaying:true||false})

新问题:如何缓存musicId以及playState
当用户退出song页面,song的实例页面会被卸载
用户如果再次进入song页面,song页面的data会被初始化
let appInstance=getApp() //获取小程序唯一的实例
实例的本质就是一个对象,我们可以对他的属性以及属性值进行任意的修改
属性名不一定要叫做globalData,可以任意取名
console.log(appInstance.globalData.msg) -> appInstance.globalData.msg=123


当用户播放歌曲或者暂停歌曲时,我们会去记录musicId以及playState

1.1,将播放和暂停歌曲封装成一个函数,

 

 

AppObject getApp(Object object)
获取到小程序全局唯一的 App 实例, 可以将数据存储在app实例上

在app.js中注册全局数据, 

globalData:{
msg:"我是初始化数据"
}
在song.js引入小程序实例
let appInstance = getApp();
app实例存储公共数据,可以对app实例对象添加数据

  data: {
    isplaying: false,
    songId: null,
    songObj: {},
    musicUrl: "",
  },

 

 // 点击播放或者暂停按钮
  async handlePlay() {
    let musicUrlData = await request("/song/url", { id: this.data.songId });
    this.setData({
      isplaying: !this.data.isplaying,
      musicUrl: musicUrlData.data[0].url,
    });
    // 获取音频实例
    // let backgroundAudioManager = wx.getBackgroundAudioManager();
    // console.log('paused',backgroundAudioManager.paused);
    // paused是只读属性,false是正在播放,true代表已停止或者暂停
    // paused值在首次读取时,一定是undefined
    //pause是方法,调用它可以暂停音频
    // if (backgroundAudioManager.paused || typeof backgroundAudioManager.paused === "undefined") {
    //   // 音频连接
    //   backgroundAudioManager.src = this.data.musicUrl;
    //   // 音频标题,必传
    //   backgroundAudioManager.title = this.data.songObj.name;
    // } else {
    //   // 暂停播放
    //   backgroundAudioManager.pause();
    // }

    // 调用方法
    this.changeAudioPlay();

    // this.setData({
    //   isplaying: !this.data.isplaying
    // })
  },
 //专门用于控制音频的播放和暂停
  changeAudioPlay() {
    /*需求:当上一首歌处于播放状态时,如果再次进入的页面的歌曲与正在播放的歌曲是同一首歌,
          就让当前页面进入播放状态
      拆分需求:
          问题一:如何知道上一首歌是哪一首
            缓存上一首歌的id
          问题二:如何知道上一首歌是否处于播放状态
            缓存上一首歌的播放状态
          问题三:如何知道用户进入该页面
            当onLoad触发时
          问题四:如何知道当前页面歌曲与上一首歌是否是同一首歌
            将上一首歌的id与当前展示页面的歌曲id进行对比
          问题五:如何让页面C3效果进入播放状态
            将data中的isplaying属性改为true
    */
    // let backgroundAudioManager = wx.getBackgroundAudioManager();
    let { isplaying, musicUrl, songObj, songId } = this.data;
    if (isplaying) {
      this.backgroundAudioManager.src = musicUrl;
      this.backgroundAudioManager.title = songObj.name;

      // 问题一: 如何知道上一首歌是哪一首
      appInstance.globalData.musicId = songId;
      // 问题二: 如何知道上一首歌是否处于播放状态
      appInstance.globalData.playState = true;
    } else {
      this.backgroundAudioManager.pause();

      // 问题一: 如何知道上一首歌是哪一首
      // 但是当代码能够进入这个判断,说明你歌曲已经处于播放状态,在播放时候已经存储过musicId
      // appInstance.globalData.musicId = songId;
      // 问题二: 如何知道上一首歌是否处于播放状态
      appInstance.globalData.playState = false;
    }
  },

当用户再次进入当前歌曲详情时,判断此时歌曲id, 在onLoad函数中判断

// 问题三: 如何知道用户进入该页面
    // 当onLoad触发时
    // 问题四: 如何知道当前页面歌曲与上一首歌是否是同一首歌
    // 将上一首歌的id与当前展示页面的歌曲id进行对比
    // 问题五: 如何让页面C3效果进入播放状态
    // 将data中的isplaying属性改为true
    let { musicId, playState } = appInstance.globalData;
    if (playState && musicId === songId) {
      this.setData({
        isplaying: true,
      });
    }
    // 将音频实例配置给当前页面实例
    this.backgroundAudioManager = wx.getBackgroundAudioManager();

 

二,此时还有个bug,点击底部背景音乐的播放按钮,上面的c3效果没有出来,而且上面还是暂停图标,

bug-2解决
问题:用户修改背景音频的渠道较多,用户可以轻易绕过我们的播放按钮的逻辑,任意控制背景音频的播放
解决:由于渠道太多,最终选择监听背景音频播放器的播放状态,并且修改对应页面的isplaying和globalData中的playState
拆解需求:
    1)如何知道背景音频播放器的播放状态
        给背景音频播放器的实例对象绑定事件监听(onPlay,onbPause,onStop),并且绑定回调函数
    2)如何修改对应页面的isplaying和globalData中的playState
        如何知道当前页面是否是背景音频正在播放的音乐的对应页面
            songId    ->    musicId
            如果是同一首歌,就可以更新当前页面的isplaying属性
        修改用于存储背景音频播放器状态的playState

2.1,封装一个函数,用来监听背景音乐的状态

BackgroundAudioManager.onPlay(function callback)
监听背景音频播放事件,背景音频播放事件的回调函数

BackgroundAudioManager.onPause(function callback)
监听背景音频暂停事件

BackgroundAudioManager.onStop(function callback)
监听背景音频停止事件
  // 专门用于绑定音频相关监听
  addAudioListener() {
    /*需求:当歌曲处于播放状态,页面的C3效果变为播放
            当歌曲处于暂停状态,页面C3效果变为暂停
      拆分需求:
        问题一:怎么知道背景音频处于什么状态
        解决:给背景音频绑定事件监听
        问题二:如何把页面的C3效果变为播放
        解决:将isplaying属性改为true
     */
    // let backgroundAudioManager = wx.getBackgroundAudioManager();
    // 绑定背景音频的播放事件
    // 虽然写法与addEventListener相似,都是函数调用,传入回调函数
    // 但是他只会是最后一个回调函数生效
    this.backgroundAudioManager.onPlay(() => {
      //问题:如果当前这首歌与上一首歌不是同一首,this不对,改成箭头函数
      console.log("play");
      if (appInstance.globalData.musicId === this.data.songId) {
        this.setData({
          isplaying: true,
        });
      }
      appInstance.globalData.playState = true;
    });
    
    // 绑定背景音频的暂停事件
    this.backgroundAudioManager.onPause(() => {
      if (appInstance.globalData.musicId === this.data.songId) {
        this.setData({
          isplaying: false,
        });
      }
      appInstance.globalData.playState = false;
    });
    // 绑定背景音频的停止事件
    this.backgroundAudioManager.onStop(() => {
      if (appInstance.globalData.musicId === this.data.songId) {
        this.setData({
          isplaying: false,
        });
      }
      appInstance.globalData.playState = false;
    });
  },

在进入页面加载onload函数时,调用该函数,判断该背景音乐的状态

this.addAudioListener();

 

三,使用npm包

使用npm包
1)package.json    ->    npm init -y
2)下载pubsub-js    ->    npm install pubsub-js    ->得到node_modules文件夹(小程序不认得该文件夹)
3)构建npm(必须去详情->本地设置->使用npm模块(勾选)),并且需要设置-构建npm    ->    把node_modules文件夹编译成miniprogram_npm
4)在页面中使用pubsub-js    ->    import PubSub from 'pubsub-js'

3.1,将获取歌曲的url封装成一个函数

  // 专门用于获取歌曲音频URL
   async getMusicUrl(){
    let musicUrlData = await request('/song/url', { id: this.data.songId });
    this.setData({
      musicUrl: musicUrlData.data[0].url
    })
    return Promise.resolve()
  },

在点击播放按钮调用

 // 点击播放或者暂停按钮
  async handlePlay() {
    this.setData({
      isplaying: !this.data.isplaying,
    })
    // 发送请求获取歌曲音频URL
    if (!this.data.musicUrl) {
      //同步代码, 调用该函数后,获取到数据后,才会调用changeAudioPlay
      await this.getMusicUrl();
    }
    // let musicUrlData = await request("/song/url", { id: this.data.songId });
    // this.setData({
    //   isplaying: !this.data.isplaying,
    //   musicUrl: musicUrlData.data[0].url,
    // });
    // 获取音频实例
    // let backgroundAudioManager = wx.getBackgroundAudioManager();
    // console.log('paused',backgroundAudioManager.paused);
    // paused是只读属性,false是正在播放,true代表已停止或者暂停
    // paused值在首次读取时,一定是undefined
    //pause是方法,调用它可以暂停音频
    // if (backgroundAudioManager.paused || typeof backgroundAudioManager.paused === "undefined") {
    //   // 音频连接
    //   backgroundAudioManager.src = this.data.musicUrl;
    //   // 音频标题,必传
    //   backgroundAudioManager.title = this.data.songObj.name;
    // } else {
    //   // 暂停播放
    //   backgroundAudioManager.pause();
    // }

    // 调用方法
    this.changeAudioPlay();

    // this.setData({
    //   isplaying: !this.data.isplaying
    // })
  },

 

四,歌曲进度条逻辑处理

 

 

播放歌曲模块(进度条功能实现)
  1)绘制静态页面
  2)在data中常见两个状态分别存储当前时间和总时间,实现页面时间动态显示
  3)下载moment插件,将时间格式转换成分秒格式
  4)通过backgroundAudioManager实例身上获取currentTime属性,可以知道当前播放时间,
     配合onTimeUpdate事件,实现定时获取播放时间效果
  5)通过当前播放时间除以总时长得到当前播放的百分比进度,并保存至状态中,动态控制进度条前进

 

number duration
当前音频的长度(单位:s),只有在有合法 src 时返回。(只读)

number currentTime
当前音频的播放位置(单位:s),只有在有合法 src 时返回。(只读

BackgroundAudioManager.onTimeUpdate(function callback)
监听背景音频播放进度更新事件,只有小程序在前台时会回调

BackgroundAudioManager.onEnded(function callback)
监听背景音频自然播放结束事件
number startTime
音频开始播放的位置(单位:s)

4.1,将歌曲详细信息封装成函数,获取音频总时长,小程序中用npm安装moment包,

引入,import moment from 'moment';

 
// 专门用于获取歌曲详细信息
  async getMusicDetail(){

    //1.请求数据
    let result = await request('/song/detail', { ids: this.data.songId });
    // console.log(result)
    // console.log('2');

    //2.保存至data中
    // console.log(moment(result.songs[0].dt).format("mm:ss"))
    console.log(result.songs[0].dt)
    this.setData({
      songObj: result.songs[0],
      // 获取总时长,时间戳,毫秒
      // durationTime: result.songs[0].dt
      durationTime: moment(result.songs[0].dt).format("mm:ss")
    })

    wx.setNavigationBarTitle({
      title: this.data.songObj.name
    })

    return Promise.resolve();
  },

 

在onload函数中调用

// 发送请求,获取歌曲详细信息
     await this.getMusicDetail();

在专门用于绑定音频相关监听的函数中,监听背景音频播放进度更新事件

// 专门用于绑定音频相关监听
  addAudioListener() {
    /*需求:当歌曲处于播放状态,页面的C3效果变为播放
            当歌曲处于暂停状态,页面C3效果变为暂停
      拆分需求:
        问题一:怎么知道背景音频处于什么状态
        解决:给背景音频绑定事件监听
        问题二:如何把页面的C3效果变为播放
        解决:将isplaying属性改为true
     */
    // let backgroundAudioManager = wx.getBackgroundAudioManager();
    // 绑定背景音频的播放事件
    // 虽然写法与addEventListener相似,都是函数调用,传入回调函数
    // 但是他只会是最后一个回调函数生效
    this.backgroundAudioManager.onPlay(() => {
      //问题:如果当前这首歌与上一首歌不是同一首,this不对,改成箭头函数
      console.log("play");
      if (appInstance.globalData.musicId === this.data.songId) {
        this.setData({
          isplaying: true,
        });
      }
      appInstance.globalData.playState = true;
    });
    
    // 绑定背景音频的暂停事件
    this.backgroundAudioManager.onPause(() => {
      if (appInstance.globalData.musicId === this.data.songId) {
        this.setData({
          isplaying: false,
        });
      }
      appInstance.globalData.playState = false;
    });
    // 绑定背景音频的停止事件
    this.backgroundAudioManager.onStop(() => {
      if (appInstance.globalData.musicId === this.data.songId) {
        this.setData({
          isplaying: false,
        });
      }
      appInstance.globalData.playState = false;
    });

    // 当背景音频时间更新时会触发
    this.backgroundAudioManager.onTimeUpdate(()=>{
      // console.log(this.backgroundAudioManager.currentTime)
      // 获取当前时长,单位秒
      let { currentTime } = this.backgroundAudioManager;
      // 获取总时长,时间戳,毫秒
      let { dt } = this.data.songObj;
      // 获取百分比,进度
      let scale = currentTime * 100000 / dt;
      currentTime = moment(currentTime*1000).format("mm:ss")
      // console.log("scale",scale)
      // console.log("currentTime", currentTime)
      this.setData({
        currentTime,
        currentWidth: scale
      })
    })

    // this.backgroundAudioManager.startTime=178;


  },
  data: {
    isplaying: false,
    songId: null,
    songObj: {},
    musicUrl: "",
    currentTime: "00:00",
    durationTime: "",
    
    currentWidth:'0'
  },

 

五,歌曲自动切换功能逻辑
 
BackgroundAudioManager.onEnded(function callback)
监听背景音频自然播放结束事件
number startTime
音频开始播放的位置(单位:s)
用npm安装PubSub, 
引入,
import PubSub from 'pubsub-js';
5.1,song页,点击上一首,或者下一首,发布消息,将他的标识id传给歌曲详情页recommendSong
<!-- 底部播放选项区域 -->
  <view class="musicControl">
    <text class="iconfont icon-iconsMusicyemianbofangmoshiShuffle" ></text>
    <text class="iconfont icon-shangyishou" id="pre"   bindtap="switchSong"></text>
    <text class="iconfont {{isplaying?'icon-zanting':'icon-bofang'}} big" bindtap="handlePlay"></text>
    <text class="iconfont icon-next" id="next"   bindtap="switchSong"></text>
    <text class="iconfont icon-iconsMusicyemianbofangmoshiPlayList"></text>
  </view>

js代码

// 用户处理用户切换歌曲的操作
   switchSong(event){
    //通过id得知用户当前点击的按钮
    let { id } = event.currentTarget;
    // console.log(event.currentTarget)
    // console.log('switchSong')
    //发布方只需要将数据传递给订阅方,所以数据类型应该是任意类型
    PubSub.publish('switchSong',id);
  },

在recommendSong页,当recommendSong页要跳转到song页,保存此时歌曲的index,当song页传来上一页和下一页的标识,然后index减一,加一,

然后在将歌曲的id传递给song页,让他去发送请求,获取上一页或者下一页歌曲的信息

  <!-- 音乐列表 -->
    <scroll-view class="scrollView" scroll-y>
      <view class="recommendItem" 
        bindtap="toSong"
        data-id="{{item.id}}"
        data-index="{{index}}"
        wx:for="{{recommendList}}" 
        wx:key="id">
  data: {
    month:"",
    day:"",
    recommendList:[],
    currentIndex:null
  },
  // 用于跳转详情页面
  toSong(event){
    let {id,index} = event.currentTarget.dataset;
    // console.log('id',id);
    this.setData({
      currentIndex: index
    })
    // 跳转song页
    wx.navigateTo({
      url: '/pages/song/song?songId=' + id
    })

  },

5.2,接收song页传过来的上一页下一页标识,然后对齐index处理,然后通过index,将歌曲id传递给song页

//订阅方需要接收数据,并且执行一定的逻辑,数据类型应该是一个函数
    PubSub.subscribe("switchSong",(msg,data)=>{
      //回调函数的第一个参数是消息名称,第二个参数才是传过来的数据
      // console.log(msg, data)
      // 定义一下这首歌当前的index
      let { currentIndex, recommendList } = this.data;
      // console.log("当前歌曲id", recommendList[currentIndex])
      // 下一首
      if(data==="next"){
        // 歌曲边界值判断
        if(currentIndex===recommendList.length-1){
          currentIndex=0
        } else {
          currentIndex++;
        }
      } else {
        // 上一首
        if(currentIndex===0){
          currentIndex = recommendList.length - 1
        } else {
          currentIndex--;
        }
      }
      // 更新当前歌曲所在下标,防止播放界面切换歌曲出现BUG
      this.setData({ currentIndex})
      // console.log("对应歌曲id", recommendList[currentIndex])
      //将对应歌曲id发送给song页面
      PubSub.publish('changeAudioId', recommendList[currentIndex].id);
    })

5.3,song页接收传过来的id,发送请求,重新获取歌曲信息

 // 订阅消息,得到传过来的歌曲id,发送请求,获取歌曲详细信息和歌曲url
    PubSub.subscribe('changeAudioId', async (msg, songId) => {
      console.log(msg, songId)
      this.setData({songId});
      // 请求歌曲详细信息,用于展示图片和歌曲名称
      await this.getMusicDetail();
      // 请求歌曲音频URL,为播放歌曲做准备
      await this.getMusicUrl();
      // 用于播放音频(但是他播放的是当前data中存放的musicUrl)
      this.changeAudioPlay();
    })

5.4,当歌曲进度条到最尾部时,自动切换到下一首,发布消息给recommendSong页

// 专门用于绑定音频相关监听
  addAudioListener() {
    /*需求:当歌曲处于播放状态,页面的C3效果变为播放
            当歌曲处于暂停状态,页面C3效果变为暂停
      拆分需求:
        问题一:怎么知道背景音频处于什么状态
        解决:给背景音频绑定事件监听
        问题二:如何把页面的C3效果变为播放
        解决:将isplaying属性改为true
     */
    // let backgroundAudioManager = wx.getBackgroundAudioManager();
    // 绑定背景音频的播放事件
    // 虽然写法与addEventListener相似,都是函数调用,传入回调函数
    // 但是他只会是最后一个回调函数生效
    this.backgroundAudioManager.onPlay(() => {
      //问题:如果当前这首歌与上一首歌不是同一首,this不对,改成箭头函数
      console.log("play");
      if (appInstance.globalData.musicId === this.data.songId) {
        this.setData({
          isplaying: true,
        });
      }
      appInstance.globalData.playState = true;
    });
    
    // 绑定背景音频的暂停事件
    this.backgroundAudioManager.onPause(() => {
      if (appInstance.globalData.musicId === this.data.songId) {
        this.setData({
          isplaying: false,
        });
      }
      appInstance.globalData.playState = false;
    });
    // 绑定背景音频的停止事件
    this.backgroundAudioManager.onStop(() => {
      if (appInstance.globalData.musicId === this.data.songId) {
        this.setData({
          isplaying: false,
        });
      }
      appInstance.globalData.playState = false;
    });

    // 当背景音频时间更新时会触发
    this.backgroundAudioManager.onTimeUpdate(()=>{
      // console.log(this.backgroundAudioManager.currentTime)
      // 获取当前时长,单位秒
      let { currentTime } = this.backgroundAudioManager;
      // 获取总时长,时间戳,毫秒
      let { dt } = this.data.songObj;
      // 获取百分比,进度
      let scale = currentTime * 100000 / dt;
      currentTime = moment(currentTime*1000).format("mm:ss")
      // console.log("scale",scale)
      // console.log("currentTime", currentTime)
      this.setData({
        currentTime,
        currentWidth: scale
      })
    })

   
    // 音频开始播放,单位秒
     this.backgroundAudioManager.startTime=178;
      // 当歌曲自动播放结束时会触发
     this.backgroundAudioManager.onEnded(()=>{
       // console.log('onEnded');
       //切换播放下一首歌曲
       PubSub.publish('switchSong',"next");
     })


  },

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2020-09-11 18:25  全情海洋  阅读(369)  评论(0编辑  收藏  举报