小程序网易云(五)
一,播放歌曲模块(播放状态显示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' },
监听背景音频自然播放结束事件
number startTime
音频开始播放的位置(单位:s)
<!-- 底部播放选项区域 --> <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"); }) },