- 小程序中实现背景音乐播放。在这个项目中,也踩了很多坑,记录一下。由于后台给我返回的音频流是m3u8格式的,没有得办法,安全性高,由于实现背景音乐播放,但是设置了
backgroundAudioManager.protocol = 'hls';
这个,导致,backgroundAudioManager.onEnded
这个api,在ios无法检测到,从18年到现在微信至今没有解决,所留下的坑。
实现功能
- 切换上下首
- 播放暂停
- 列表播放
- 随机播放
- 单曲播放
- 定时播放
- 拖拽播放
- 定位播放当前时间
背景音乐播放
- 直接使用微信提供的api即可,
backgroundAudioManager
。从列表页进入这个播放详情页,在onLoad
调用这个方法即可,然后需要传入一个id
。
_loadMusicDetail(musicId) {
var i = wx.getStorageSync("openkey");
let _this = this;
if (null != i && "" != i) {
var r = url + "&do=DetailVoice", d = {
id: musicId,
openkey: i
};
wx.showLoading({
title: '网络加载中...',
})
http.Post(r, d, function (res) {
if (res) {
if (res.index == 0) {
_this.setData({
pre_ok: false
})
} else {
_this.setData({
pre_ok: true
})
}
wx.setNavigationBarTitle({
title: res.bookname,
})
wx.setStorageSync('voiceData', res)
//设置全局播放
backgroundAudioManager.src = res.video_addr
backgroundAudioManager.title = res.title;
backgroundAudioManager.coverImgUrl = res.top_pic;
backgroundAudioManager.protocol = 'hls';
_this.setData({
coursedata: res,
listdata: res.datalist,
isPlaying: true,
next_ok: true,
videoid: res.id,
video_addr: res.video_addr
})
app.globalData.vid = res.id; //全局id
wx.hideLoading()
}
})
}
},
播放器初始化
- 定义播放器这个api的初始化状态,然后在onload调用一下,否则不是执行播放。
- 用于处理播放器时间,获取播放器时长
_setTime
单独处理处理,用于播放总时长_bindBGMEvent
方法里面包括了对小程序整个背景音乐播放的核心,backgroundAudioManager.onTimeUpdate
这个api监听背景音乐播放进度更新事件,backgroundAudioManager.onEnded
监听背景音乐播放结束,但是由于我们设置了backgroundAudioManager.protocol = 'hls'
这个。所以在ios中无法检测。通过其他方法处理一下。来检测是否播放结束。通过判断当前播放时长currentTimes
和总时长duration
比较,实现切换下一首歌曲_this.OnNext()
- 播放切换下一首歌曲
if ((Number(_this.data.currentTimes) + 1 | 0) >= (Number(_this.data.duration) | 0) && Number(_this.data.currentTimes) > 0 && Number(_this.data.duration) > 0) {
// 监听放播放当前歌曲结束时,自动切换下一首
if (!flag) {
_this.OnNext()
flag = true
}
} else {
flag = false
}
- 运行到后台却不能自动切换下一首。只要安卓可以。
判断机型
- 目前实现的方法,只能先让安卓实现运行到后台可以接着播放,ios却不行,在data定义
playSystem
字段,如果是安卓设置为0,否则为1。这样在安卓的时候可以实现backgroundAudioManager.onEnded
。
_getSystemInfoSync() {
let _this = this;
wx.getSystemInfo({
success: (res) => {
if(res.system.includes('Android ')) {
console.log('安卓')
_this.setData({
playSystem:0
})
} else {
_this.setData({
playSystem:1
})
}
},
})
},
列表循环
- 做列表,单曲,随机播放这功能是,我是在data定义一个````isSingle```一个字段。如果为0就是列表,1代表单曲。2代表随机播放。
- 列表循环好做一下。播放完之后,调用一下
_this.OnNext()
即可。
orderPlay() {
// 列表循环
this.setData({
isSingle: 1
})
wx.showToast({
title: '单曲循环',
icon: "none"
})
},
在backgroundAudioManager.onTimeUpdate
实现
if (this.data.isSingle === 0 && this.data.playSystem == 1) {
console.log('1')
//列表循环 只适用于ios
if ((Number(_this.data.currentTimes) + 1 | 0) >= (Number(_this.data.duration) | 0) && Number(_this.data.currentTimes) > 0 && Number(_this.data.duration) > 0) {
// 监听放播放当前歌曲结束时,自动切换下一首
if (!flag) {
_this.OnNext()
flag = true
}
} else {
flag = false
}
}
单曲循环
- 单曲循环,就是一直播放首歌曲就行,不调用下一首即可
singlePlay() {
// 单曲循环
this.setData({
isSingle: 2
})
wx.showToast({
title: '随机循环',
icon: "none"
})
},
在backgroundAudioManager.onTimeUpdate
实现
if (this.data.isSingle === 1 && this.data.playSystem == 1) {
// 单曲循环
if ((Number(_this.data.currentTimes) + 1 | 0) >= (Number(_this.data.duration) | 0) && Number(_this.data.currentTimes) > 0 && Number(_this.data.duration) > 0) {
// 监听放播放当前歌曲结束时,自动切换下一首
this._loadMusicDetail(this.data.videoid)
}
}
随机播放
- 随机播放,就需要单独处理一下,需要对
listdata
数组打乱,取某一个索引值,取到id,通过调用this._loadMusicDetail
传过去就行
randomPlay() {
// 随机循环
this.setData({
isSingle: 0
})
wx.showToast({
title: '列表循环',
icon: "none"
})
},
在backgroundAudioManager.onTimeUpdate
实现
if(this.data.isSingle === 2 && this.data.playSystem == 1) {
// 随机播放
if ((Number(_this.data.currentTimes) + 1 | 0) >= (Number(_this.data.duration) | 0) && Number(_this.data.currentTimes) > 0 && Number(_this.data.duration) > 0) {
// 监听放播放当前歌曲结束时,自动切换下一首
if (!flag) {
let data = this.data.listdata;
let index = Math.floor(Math.random() * data.length);
let item = data[index]
this._loadMusicDetail(item.id)
flag = true
} else {
flag = false
}
}
}
- 初始化
_bindBGMEvent() {
let _this = this;
let flag = false
backgroundAudioManager.onCanplay(() => {
// 监听背景音频进入可播放状态事件。 但不保证后面可以流畅播放
if (typeof backgroundAudioManager.duration != 'undefined') {
//获取音频总时间
_this._setTime()
} else {
setTimeout(() => {
_this._setTime()
}, 1000)
}
})
backgroundAudioManager.onTimeUpdate(() => {
let currentTime = backgroundAudioManager.currentTime //当前播放进度时间
let duration = backgroundAudioManager.duration //总时长
const durationFmt = _this._dateFormat(duration)
// 判断时间是否有想等的
const currentFmt = _this._dateFormat(currentTime);
// 获取到播放实时进度,实现从首页进入播放页面,接着播放
wx.setStorageSync('currentTime', currentTime)
_this.setData({
progress: currentTime / duration * 100 | 0,
duration: duration,
currentTimes: currentTime,
currentTime: `${currentFmt.min}:${currentFmt.sec}`,
totalTime: `${durationFmt.min}:${durationFmt.sec}` //总时长
})
if (this.data.isSingle === 1 && this.data.playSystem == 1) {
// 单曲循环
if ((Number(_this.data.currentTimes) + 1 | 0) >= (Number(_this.data.duration) | 0) && Number(_this.data.currentTimes) > 0 && Number(_this.data.duration) > 0) {
// 监听放播放当前歌曲结束时,自动切换下一首
this._loadMusicDetail(this.data.videoid)
}
} else if(this.data.isSingle === 2 && this.data.playSystem == 1) {
// 随机播放
if ((Number(_this.data.currentTimes) + 1 | 0) >= (Number(_this.data.duration) | 0) && Number(_this.data.currentTimes) > 0 && Number(_this.data.duration) > 0) {
// 监听放播放当前歌曲结束时,自动切换下一首
if (!flag) {
let data = this.data.listdata;
let index = Math.floor(Math.random() * data.length);
let item = data[index]
this._loadMusicDetail(item.id)
flag = true
} else {
flag = false
}
}
}
if (this.data.isSingle === 0 && this.data.playSystem == 1) {
console.log('1')
//列表循环 只适用于ios
if ((Number(_this.data.currentTimes) + 1 | 0) >= (Number(_this.data.duration) | 0) && Number(_this.data.currentTimes) > 0 && Number(_this.data.duration) > 0) {
// 监听放播放当前歌曲结束时,自动切换下一首
if (!flag) {
_this.OnNext()
flag = true
}
} else {
flag = false
}
}
})
backgroundAudioManager.onEnded(() => {
// 监听播放完 只适用于安卓
console.log(_this.data.playSystem,'end')
if(this.data.isSingle === 0 && _this.data.playSystem == 0) {
if (!flag) {
_this.OnNext()
flag = true
} else {
flag = false
}
}
if(this.data.isSingle === 1 && _this.data.playSystem == 0) {
this._loadMusicDetail(this.data.videoid)
}
if(this.data.isSingle === 2 && _this.data.playSystem == 0) {
if (!flag) {
let data = this.data.listdata;
let index = Math.floor(Math.random() * data.length);
let item = data[index]
this._loadMusicDetail(item.id)
flag = true
} else {
flag = false
}
}
})
},
- 处理播放总时长以及播放时间
_setTime() {
let _this = this;
duration = backgroundAudioManager.duration //获取播放总时长
const durationFmt = this._dateFormat(duration)
_this.setData({
totalTime: `${durationFmt.min}:${durationFmt.sec}` //总时长
})
},
_dateFormat(sec) {
//格式化时间
const min = Math.floor(sec / 60) //分钟
sec = Math.floor(sec % 60) //秒
return {
'min': this._parse0(min),
'sec': this._parse0(sec)
}
},
_parse0(sec) {
// 补0
return sec < 10 ? '0' + sec : sec
},
拖拽播放
- 拖拽进度条,拖拽到某个位置播放,微信也提供了api方法
backgroundAudioManager.seek
,传入处理好的秒
SliderChange(e) {
let _this = this;
let value = e.detail.value;
console.log(value, 'value')
_this.data.progress = value;
backgroundAudioManager.seek(backgroundAudioManager.duration * value / 100);
},
播放与暂停
- 其实播放与暂停只需要调用一下提供的api即可。其实在
data
里面定义一个变量,用来判断当前是播放状态还是暂停状态isPlaying
。
OnPlayPause() {
// 播放事件
if (this.data.isPlaying) {
//正在播放,点击暂停
backgroundAudioManager.pause()
} else {
// 点击播放
backgroundAudioManager.play()
}
this.setData({
isPlaying: !this.data.isPlaying
})
},
点击上一首
- 点击上一首其实就是切换index,然后就是判断索引是否大于长度,如果大于就不让切换了,否则继续。
listdata
就是列表,coursedata
当前播放的数据。
OnNext() {
// 下一首
var _this = this;
let listdata = _this.data.listdata;
let { index } = _this.data.coursedata;
_this.setData({
next_ok: true,
pre_ok: true
})
let i = ++index
if (i >= listdata.length) {
_this.setData({
next_ok: false,
})
return
}
console.log(listdata,'listdata')
this._loadMusicDetail(listdata[i].id)
wx.removeStorageSync('voiceData')
wx.removeStorageSync('currentTime')
},
点击下一首
- 点击下一首和点击上一首其实大差不差。逻辑几乎一模一样,一个加,一个减而已。
OnPre() {
// 播放上一首
var _this = this;
let listdata = _this.data.listdata;
let { index } = _this.data.coursedata;
_this.setData({
pre_ok: true,
next_ok: true,
})
if (index <= 0) {
_this.setData({
pre_ok: false
})
index = 0
return
}
this._loadMusicDetail(listdata[--index].id);
wx.removeStorageSync('voiceData')
wx.removeStorageSync('currentTime')
},
定时关闭处理之一
- 首先是在data定义一个数组
array: ['不开启', '播放10分钟', '播放20分钟', '播放30分钟', '播放60分钟', '自定义'],
- 当点击自定义的时候,就是一个时间
- 其实我做这个的时候,比如定义一个10分钟的话,就是10*60秒,最后得到600秒,然后调用一个方法,传过去,通过定时器,当时间结束之后,调用
backgroundAudioManager.pause()
关闭播放
bindPickerChange(e) {
// 定时播放功能
let i = e.detail.value
if (i == 0) {
let time = '';
let value = '关闭定时器'
this.setData({
dateTime: "",
dateTimeMin: "",
dateTimeSec: ""
})
this.timeDate(time, value)
} else if (i == 1) {
let time = 10 * 60;
let value = '播放10分钟'
this.timeDate(time, value)
} else if (i == 2) {
let time = 20 * 60;
let value = '播放20分钟'
this.timeDate(time, value)
} else if (i == 3) {
let time = 30 * 60;
let value = '播放30分钟'
this.timeDate(time, value)
} else if (i == 4) {
let time = 60 * 60;
let value = '播放60分钟'
this.timeDate(time, value)
} else {
//自定义时间
this.setData({
isPickChangeList: false,
isPickChangeTime: true
})
}
},
isPickChangeTime
设置为true的话,就是自定义的时间
- 对时间处理好之后,调用
timeDate
方法即可
bindTimeChange(e) {
//自定义时间播放
let date = e.detail.value.split(':')
let h = Number((date[0] * 1) * 3600)
let s = Number((date[1] * 1) * 60)
let time = h + s
if (time > 0) {
this.timeDate(time, '')
this.setData({
isPickChangeTime: false,
isPickChangeList: true
})
} else {
wx.showToast({
title: '请选择设置时间',
icon: "none"
})
this.setData({
isPickChangeList: false,
isPickChangeTime: true
})
}
},
timeDate
是对传过来的时间处理time
传过来的时间(秒)value
提示内容
timeDate(time, value) {
let flag = false
clearInterval(this.data.isTimePick)
if (!flag) {
if (value != '') {
wx.showToast({
title: value,
icon: "none"
})
}
if (time > 0) {
this.data.isTimePick = setInterval(() => {
time--
if (time === 0) {
this.setData({
dateTime: "",
dateTimeMin: "",
dateTimeSec: "",
isPlaying: false
})
backgroundAudioManager.pause()
clearInterval(this.data.isTimePick)
wx.removeStorageSync('timingTime')
return
} else {
this.setData({
dateTime: Math.floor(time / 3600) < 10 ? '0' + Math.floor(time / 3600) : Math.floor(time / 3600),
dateTimeMin: Math.floor((time / 60 % 60)) < 10 ? '0' + Math.floor((time / 60 % 60)) : Math.floor((time / 60 % 60)),
dateTimeSec: Math.floor((time % 60)) < 10 ? '0' + Math.floor((time % 60)) : Math.floor((time % 60))
})
}
wx.setStorageSync('timingTime', time)
}, 1000);
}
} else {
flag = false
}
},
定位播放当前时间
- 就是在详情播放背景音乐的时候。返回首页,想从首页一个按钮点击回到详情,并且定位到播放的位置。不能从头开始播放
- 听到这个需求的时候,想到的就是通过本地存储。通过在
backgroundAudioManager.onTimeUpdate
方法执行本地存储。把每次播放的当前时间存储起来。
wx.setStorageSync('currentTime', currentTime)
- 在首页,先取到本地的时间。还有详情的id
PlayStatusBtn: function () {
let currentTime = wx.getStorageSync('currentTime')
let timingTime = wx.getStorageSync('timingTime')
wx.getBackgroundAudioPlayerState({
success: function (t) {
2 == t.status || null == o.globalData.vid ? wx.showModal({
title: "提示",
content: "没有正在播放的内容",
showCancel: !1,
success: function (t) { }
}) : wx.navigateTo({
url: "../detail/voice?id=" + o.globalData.vid +'¤tTime=' + currentTime+'&timingTime='+timingTime
});
},
});
},
- 详情页中,在onload中执行,
voiceData
当前播放详情的数据,也是请求的是,把这个数据存在本地,只要当从首页进入的时候,在读取。 - 通过判断time是否有值,如果有值,说明是从首页跳转过来的,否则直接调用
_this._loadMusicDetail(ids);
方法就行
let time = e.currentTime; //获取页面跳转时间
if (time && time > 0) {
let res = wx.getStorageSync('voiceData')
if (res.index == 0) {
_this.setData({
pre_ok: false
})
} else {
_this.setData({
pre_ok: true
})
}
backgroundAudioManager.src = res.video_addr
backgroundAudioManager.title = res.title;
backgroundAudioManager.coverImgUrl = res.top_pic;
backgroundAudioManager.protocol = 'hls';
setTimeout(() => {
backgroundAudioManager.seek(parseFloat(time))
}, 1000)
_this.setData({
coursedata: res,
listdata: res.datalist,
videoid: res.id,
isPlaying: true,
next_ok: true,
video_addr: res.video_addr
})
app.globalData.vid = res.id; //全局id
_this.LoadComment(); //评论信息
} else {
_this._loadMusicDetail(ids);
}
- 以上就是关于背景音乐播放的全部代码,只提供大概思路。可以参考一下,如果谁工作中遇到问题,设置
backgroundAudioManager.protocol = 'hls';
在ios无法检测到backgroundAudioManager.onEnded
这个api,有啥更好的办法,可以私聊一下哈