文章目录
前文:
微信小程序学习之旅–第一个页面的制作
微信小程序学习之旅–零基础制作自己的小程序–第二个页面的制作
微信小程序学习之旅–完善pages页面–字体图标,数据绑定,条件/列表渲染,事件/catch/bind以及路由的学习
微信小程序学习之旅–零基础制作自己的小程序-完成文章详情页–自定义属性/页面通信/缓存机制/异步API/async/await
微信小程序入门(五)
音乐播放功能
制作样式
静态样式没啥好说的,基本上有手就行了。
<view class="head-image-audio">
<image class="head-image" src="{{imgSrc}}"></image>
<!-- 实现音乐播放 -->
<image class="audio" src="/images/music/music-start.png" />
</view>
/* 大图+音乐播放 */
.head-image-audio {
position: relative;
height: 460rpx;
}
/* 图片 */
.head-image {
width: 100%;
height: 100%;
}
/* 音乐播放 */
.audio {
width: 102rpx;
height: 110rpx;
position: absolute;
/* 居中 */
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
opacity: .6;
}
我们想要实现的功能就是点击以后实现音乐的播放。
背景音频播放
这里也需要了解一下官方提供的关于背景音频播放的API组件。
绑定事件
老规矩,肯定还是需要在图片上绑定点击事件。
<!-- 实现音乐播放 -->
<image bind:tap="onMusic" class="audio" src="/images/music/music-start.png" />
功能测试
这里先简单的使用一下背景音频。
/**
* 音乐播放功能
* @param {*} event
*/
onMusic(event) {
// 获取全局唯一的背景音频管理器。 小程序切入后台,如果音频处于播放状态,可以继续播放。但是后台状态不能通过调用API操纵音频的播放状态
const mgr = wx.getBackgroundAudioManager();
// 赋值音乐播放的链接
mgr.src = this.data.music.url;
// 音乐播放的标题
mgr.title = this.data.music.title;
}
咳咳,很明显,没什么大毛病。用起来也不难。
控制台报错
虽然使用没什么问题,但是控制台给我们了报错提示。这时候,我们去这个地址看看。
为了能够转后台以后可以继续播放音频。我们就在app.json文件中配置一下。
当我们在app.json中配置了这个属性,并且数组里面只有一个值audio时
"requiredBackgroundModes": ["audio"]
音乐播放的同时,切后台音乐也会一直播放。
而只使用location这个值,只有在小程序没有退出的时候才会一直播放,一旦切后台,音乐就会停止。
"requiredBackgroundModes": ["location"]
我们这里就把两个值都加上吧。
"requiredBackgroundModes": ["audio","location"]
切换音乐播放图标
因为需要进行音乐播放与停止的切换,所以图标也需要更改。
<!-- 实现音乐播放 -->
<!-- 播放和暂停图标的切换 条件渲染 -->
<!-- 当然这里用三元表达式就不需要使用两个标签了 -->
<image wx:if="{{!isPlaying}}" bind:tap="onMusicStart" class="audio" src="/images/music/music-start.png" />
<image wx:else bind:tap="onMusicStop" class="audio" src="/images/music/music-stop.png" />
/**
* 音乐播放开始功能
* @param {*} event
*/
onMusicStart(event) {
// 获取全局唯一的背景音频管理器。 小程序切入后台,如果音频处于播放状态,可以继续播放。但是后台状态不能通过调用API操纵音频的播放状态
const mgr = wx.getBackgroundAudioManager();
// 赋值音乐播放的链接
mgr.src = this.data.music.url;
// 音乐播放的标题
mgr.title = this.data.music.title;
// 播放时使用的图片
mgr.coverImgUrl = this.data.music.coverImg;
// 切换当前音乐的播放状态为 播放中
this.setData({
isPlaying: true
});
},
/**
* 音乐暂停的事件处理
* @param {*} event
*/
onMusicStop(event) {
const mgr = wx.getBackgroundAudioManager();
// 音乐停止
mgr.stop();
// 音乐状态的切换
this.setData({
isPlaying:false
})
},
说实话,这样的确达到了音乐的停止和播放之间状态的切换。但是,你会发现每次切换播放和暂停音乐以后,音乐需要重新播放、并且,如果在音乐播放的时候,我们点击下面的控制面板,可以发现,音乐暂停了,但是图标不会自动切换。问题还是很多。
同步音乐控制开关与图标的状态
背景音频对象的播放/暂停事件回调
很明显,想要达到我们需要的某些状态,肯定是需要使用音频对象提供的某些回调函数。
所以我们通过这两个事件,来根据音乐的播放和暂停调用不同的函数。
进一步优化
因为我们会在很多地方都使用到这个背景音频的对象,所以我们将其放到data数据源中,并且在页面加载的时候进行获取,同时在onLoad中监听音乐的播放和暂停两个事件。
/**
* 页面的初始数据
*/
data: {
post: {},
// 记录当前文章的id
_pid: null,
// 当前文章是否收藏
collected: false,
// 所有文章是否收藏的缓存对象
_postsCollected: {},
// 音乐是否播放中
isPlaying: false,
// 背景音频对象
_mgr: null
},
/**
* 生命周期函数--监听页面加载
*/
async onLoad(options) {
// 通过监听页面加载的的函数的参数options,机也可以拿到我们传递过来本页面的参数
// 注意:查询字符串(参数)的类型都是字符串类型
// console.log(options);
const [post] = [...postList.filter(post => post.postId === parseInt(options.pid))];
// 记录文章id
this.data._pid = post.postId;
// console.log(post);
// 拿到我们指定的文章
this.setData(post);
// 读取缓存,拿到文章是否收藏的状态
const { data: postsCollected } = await wx.getStorage({
key: 'post_collected',
})
// 记录所有文章的缓存状态的对象
this.data._postsCollected = postsCollected;
// 拿到当前文章的收藏状态 取不到则表示这篇文章未收藏
const collected = postsCollected[this.data._pid] || false;
// console.log(postsCollected);
// console.log(collected);
this.setData({ collected });
const mgr = wx.getBackgroundAudioManager();
this.data._mgr = mgr;
// 监听背景音乐的播放和停止
// mgr.onPlay(()=>{
// // 音乐播放
// this.onMusicStart();
// });
mgr.onPlay(this.onMusicStart);
// 音乐暂停的回调处理
mgr.onPause(this.onMusicStop);
},
/**
* 文章收藏点击事件的回调
* @param {*} event 事件对象
*/
async onCollect(event) {
const postCollected = this.data._postsCollected;
// 当前文章的收藏状态 取不到则肯定没有收藏过
const collected = postCollected[this.data._pid] || false;
// 用文章id的值作为实际存储对象的属性,属性值是是否收藏
postCollected[this.data._pid] = !collected;
wx.setStorageSync('post_collected', postCollected)
this.setData({
// 这里直接取反就可以了 上面的执行完毕这里可以直接取反了
collected: !collected
});
// 使用小程序默认的API 弹框组件来提示用户是收藏文章 还是取消收藏
wx.showToast({
// 提示文字
title: this.data.collected ? "收藏文章成功!" : "取消收藏成功!",
// 提示框停留时间
duration: 2000
})
},
/**
* 完成文章分享的回调函数
* @param {*} event
*/
onShare(event) {
// 调用小程序原生的组件 实现分享
wx.showActionSheet({
// 分享的方式(具体分享以后做什么,我们并没有做)
itemList: ["分享到QQ", "分享到微信", "分享到微博", "分享到朋友圈"],
success(res) {
// 通过 res.tapIndex 可以拿到我们点击了哪一个数组元素的索引
// 想做其他事情可以做
// console.log(res.tapIndex);
}
})
},
/**
* 音乐播放开始功能
* @param {*} event
*/
onMusicStart(event) {
const mgr = this.data._mgr;
// 赋值音乐播放的链接
mgr.src = this.data.music.url;
// 音乐播放的标题
mgr.title = this.data.music.title;
// 播放时使用的图片
mgr.coverImgUrl = this.data.music.coverImg;
// 切换当前音乐的播放状态为 播放中
this.setData({
isPlaying: true
});
},
/**
* 音乐暂停的事件处理
* @param {*} event
*/
onMusicStop(event) {
const mgr = this.data._mgr;
// 音乐停止
mgr.stop();
// 暂停播放
// mgr.pause();
// 音乐状态的切换
this.setData({
isPlaying: false
})
},
这样就可以完成音乐的停止和播放之间的切换了。
但是,这样做我们音乐就直接停止了,用户点击的是暂停按钮,按理说我们要做的应该是让音乐暂停,而不是直接终止。
很显然,用户点击控制面板的暂停,应该就是让音乐暂停,而不是直接停止。
所以说,我们要做的,应该是点击我们自己的按钮,实现的是音乐的播放和停止,而用户点击下面的控制面板,应该是在播放和暂停之间切换。
再一次优化音乐的暂停和停止
话都说的这么明白了,接下来不用我说,有手就行了。
这里将音乐停止的函数进行改进。
/**
* 音乐暂停的事件处理
* @param {*} isStop 停止音乐
*/
onMusicStop(isStop = true) {
// const mgr = wx.getBackgroundAudioManager();
const mgr = this.data._mgr;
if (isStop) {
// 音乐停止
mgr.stop();
} else {
// 暂停播放
mgr.pause();
}
// 音乐状态的切换
this.setData({
isPlaying: false
})
},
async onLoad(options) {
// 省略 。。。
const mgr = wx.getBackgroundAudioManager();
this.data._mgr = mgr;
mgr.onPlay(this.onMusicStart);
// 音乐暂停的回调处理
mgr.onPause(this.onMusicStop.bind(this,false));
}
这就达到了我们想要的效果。点击下面的控制面板,音乐暂停;点击上面图片按钮,音乐由播放转为停止。
音乐播放状态的初始化问题
当我们解决了上面的问题之后,接下来其实很有其他问题。
当我们点击返回按钮,此时音乐还是处于播放状态,因为我们没有停止播放。再次进行本页面的时候,这个音乐还是播放中,但是我们的图标和下面音乐的状态对不上了。
原因当然很明显,是我们的状态问题,每次我们页面的加载时,状态都被重置为false。
这里我们可以采用全局变量的方式,来记录音乐的播放与否。没必要使用缓存,因为缓存是持久化的。实际上程序退出了就没必要管音乐的事情了。
在优化
这思路就很简单了。搞一个全局变量,每次在音乐状态切换的同时,我们全局的状态跟着改变。
// app.js
App({
// 记录音乐的播放状态
gIsPlayingMusic:false
})
// // pages/post-detail/post-detail.js
// 全局的小程序对象,用来获取我们的全局变量
const app = getApp()
Page({
onLoad(){
// 获取全局记录的播放状态
this.setData({
isPlaying: app.gIsPlayingMusic
});
},
/**
* 音乐播放开始功能
* @param {*} event
*/
onMusicStart(event) {
// 改变全局的音乐播放状态
app.gIsPlayingMusic = true;
},
/**
* 音乐暂停的事件处理
* @param {*} isStop 停止音乐
*/
onMusicStop(isStop = true) {
// 改变全局的音乐播放状态
app.gIsPlayingMusic = false;
}
})
其它代码都不在赘述。
分析其它问题
不是问题也是问题的问题
老实说,改了这么多,还是有一些小的bug。刚解决一个又来一个。
这个问题说是问题,也可以说不是问题。
就是我们发现,我们当前文章的歌曲在播放,然后我们退出去进入其他页面,按理说其他页面的歌曲和我们这个是不一样的歌曲,应该是不应该让音乐按钮显示播放状态的,但是我们每次获取的状态都是全局的,所以导致只要有音乐在播放,进去其他文章也都是会显示播放状态。
这个问题你说是问题,其实也没错,本来就是bug。你要是说不是问题,也没毛病,本来就是有音乐就是在播放中。
这里我理解呢是bug。就简单的解决一下。
思路还是那么明显,搞个全局变量就完事。弄一个全局变量记录当前音乐播放是属于那篇文章的。这样在赋值播放状态的时候,我们就可以根据文章的id号是否相同,来解决这个问题。
全局变量
// app.js
// 记录音乐的播放状态
gIsPlayingMusic: false,
// 记录播放的音乐是属于那篇文章的 默认是-1
gIsPlayingPostId: -1
文章音乐播放状态
/**
* 当前文章的音乐播放状态
*
* @returns 只有音乐所属的文章号是当前所在的文章页面,且音乐一直处于播放状态, 返回true
*/
currentPostMusicIsPlaying() {
if (app.gIsPlayingMusic && this.data._pid === app.gIsPlayingPostId)
return true;
return false;
}
文章音乐暂停和停止
音乐暂停的逻辑不需要改变,音乐停止的逻辑进行修改
/**
* 音乐暂停的事件处理
* @param {*} isStop 停止音乐
*/
onMusicStop(isStop = true) {
const mgr = this.data._mgr;
if (isStop) {
// 音乐停止
mgr.stop();
// 音乐停止,重置全局音乐的文章id
app.gIsPlayingPostId = -1;
} else {
// 暂停播放
mgr.pause();
}
// 音乐状态的切换
this.setData({
isPlaying: false
})
// 改变全局的音乐播放状态
app.gIsPlayingMusic = false;
}
onLoad初始化的时候赋值
onLoad(){
// 音乐播放状态 只有音乐所属的文章号是当前所在的文章页面,且音乐一直处于播放状态,我们才重置音乐的播放状态为true
this.setData({
isPlaying: this.currentPostMusicIsPlaying()
});
}
还未解决的bug
解决了很多问题。但是还是存在一些问题。
音乐播放完毕,图标不同步
当我们音乐播放完毕后,上面的图标并没有同步的进行状态的切换,音乐都播放完了,按理说状态应该是未播放。
音乐播放时进入其他文章
表面上,我们解决了这些问题,但是进去其他页面的时候,如果我们点击了音乐的暂停,这很ok。如果暂停后再次点击播放,播放的音乐就变成当前文章的背景音乐了。我觉得这也不是很合理的。
bug很多。头疼。