• 小程序网易云(五)


    一,播放歌曲模块(播放状态显示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");
         })
    
    
      },
     
  • 相关阅读:
    RecyclerView的万能适配器+定义可以到底部自动刷新的RecyclerView
    Material Design 摘要
    模版方法模式
    工厂方法模式
    单例模式
    Android中使用Intent和IntentFilter进行通信
    Json/XML/HTML数据解析
    Java中集合总结
    重构笔记
    Android中ActionBar的使用
  • 原文地址:https://www.cnblogs.com/fsg6/p/13653299.html
Copyright © 2020-2023  润新知