声明该小程序所有文件内容均来自于网易云音乐
音乐治疗室 MusicCureRoom
写文章不易 动动小手点个赞吧!谢谢。
源码地址可取音乐治疗室
测试小程序,可扫码,查看效果。
新版体验,可申请测试
实现了进度条拉伸,自动播放下一首歌曲
最近,微信小程序课程开发中老师在学习通布置了一个这样的任务。写一个音乐播放器。
大抵功能是实现音乐的播放,暂停,切换歌曲,以及音乐列表和播放进度条等。
于是我开启了小程序开发的路程。
以下先列举一下小程序入门需要的文档。
-
该文档包含微信小程序开发中所有组件api的介绍以及使用。
-
小程序开发上传都需要进行注册,在这里我们可以注册自己需要写的小程序。注册 完我们就可以登录进入我们的小程序后台,对我们的小程序进行管理。
-
在这里下载好我们适配的版本工具,我们的小程序就是在这里被创建的。
开启微信小程序项目之路
浅看一下最后的效果吧!
前端人做项目必写数据
这里可以看我上一篇文章,前端爬数据当接口 你还在一个一个的写json数据吗?详细教学,先看一看,在这个音乐里我们需要定义的一些数据。
data: {
//全局定义的数据
src: 'http://music.163.com/song/media/outer/url?id=1463165983.mp3',
name: '天外来物',
author: '薛之谦',
poster: '//p2.music.126.net/MgH6SepYHboKPr6FR8yg-w==/109951167040040692.jpg?param=90y90',
loop: false, // 判断是否需要循环播放
show: false, // 判断是否处于播放状态还是暂停状态
currentSecond: '00:00', //页面所需当前播放的时间
curSecond: 0, // 当前播放的秒数
duration: "04:18", // 音频显示总时长
curduration: 0, //音频总时长
//每首音乐所需要定义的数据
songs:[{
src: 'http://music.163.com/song/media/outer/url?id=1463165983.mp3',
name: '天外来物',
author: '薛之谦',
poster: '//p2.music.126.net/MgH6SepYHboKPr6FR8yg-w==/109951167040040692.jpg?param=90y90'
}]
}
首先我们看到每首歌有src,name,author,poster属性。
我们用浏览器打开网易云音乐,随便进入一个歌单,可以看到这样的效果。
因为这种页面里,没有图片,所以当我们拿爬虫去爬的时候,拿不到图片,点开歌曲,图片才会显示。
按照之前的方法我们应该可以拿到name,author,注意我点击的这个id,这是每首网易云歌曲播放的id,我们可以拿到这个id,然后通过一个正则表达式和字符串拼接拿到src。
重要的src,重要的src,重要的src
let $ = cheerio.load(html)
由于body 太多层 我就不细写了 不懂可以看上一篇文章教程
我就直接 f-cb 开始
let href = $('.f-cb .tt .ttc .txt a').attr('href')
.attr 是 拿到 a 标签中 href 的值
正则拿取其中的数字
let id = href.replace(/[^\d]/g, "")
字符串拼接
let src = 'http://music.163.com/song/media/outer/url?id=' + id + '.mp3'
比如
http://music.163.com/song/media/outer/url?id=202369.mp3
这样就拿到了能播放的src
测试 赵雷——画
介绍
body
注释的部分是之前采用老式的音频api wx.createAudioContext 功能描述 创建的 它需要将所有的东西绑定到 audio 中。依靠绑定audio中的id将音频与它关联起来。
新版采取wx.createInnerAudioContext个人认为二者区别不大,新版的加了一些新的属性,音乐播放时长duration,currentTime。新版采取直接利用创建的createInnerAudioContext拿取当前音频的各个信息。
图片加了一个旋转类,动态绑定,当音乐处于播放状态,会添加旋转类,暂停就删除旋转类。
wxml
body 部分
包含音乐名以及 图片
<view class="body">
<view class="text">
{{name}}--{{author}}
</view>
注释的部分是之前采用老式的音频api 创建的 它需要将所有的东西绑定到 audio 中
<!-- <audio src="{{src}}" id="myAudio" poster="{{poster}}" name="{{name}}" author="{{author}}" loop="{{loop}}"></audio> -->
动态绑定类
<view class="image {{!show ? ' ':'rotate'}}">
<image src="{{poster}}" mode="" />
</view>
</view>
js
onShow() {
let audioCtx = this.audioCtx
audioCtx.autoplay = false
audioCtx.src = this.data.src
audioCtx.loop = this.data.loop
},
css
.body {
height: 65vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.text {
text-align: center;
width: 600rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 50rpx;
color: #fff;
font-size: 1.5rem;
}
.image image {
border-radius:50%;
opacity: 0.6;
width: 450rpx;
margin-top: 30rpx;
height: 450rpx;
}
.rotate{
display: block;
animation: rotate 6s linear infinite
}
@keyframes rotate{
0%{
transform: rotateZ(0deg);/*从0度开始*/
}
100%{
transform: rotateZ(360deg);/*360度结束*/
}
}
slider
该部分主要是音频的滑动条以及音频播放时长以及总时长。 之前我们看到我们定义了四个属性。
currentSecond: '00:00', //页面所需当前播放的时间
curSecond: 0, // 当前播放的秒数
duration: "04:18", // 音频显示总时长
curduration: 0, //音频总时长
wxml
<view class="slide">
<view class="body-view">
<slider class="color" bindchanging="sliderChangeing" bindchange="sliderChange" min="0" max="{{curduration}}" value="{{curSecond}}" activeColor="#F91C55" block-size="18" />
</view>
<view class="slide_text">
<text>{{currentSecond}}</text>
<text>{{duration}}</text>
</view>
</view>
js
sliderChangeing:function(e){
// 在滑动滚动条的时候,暂停播放
this.pause();
let time =e.detail.value;
//通过 this.audioCtx.seek 将音频跳转至当前时间
this.audioCtx.seek(time)
// 修改 显示的 值 重新赋值
this.setData({
curSecond:time,
currentSecond:format(time)
})
},
// 滑动结束 开始播放歌曲
sliderChange:function(e){
this.play()
},
// 将时间转换成字符串显示
function format(t) {
let time = Math.floor(t / 60) >= 10 ? Math.floor(t / 60) : '0' + Math.floor(t / 60)
t = time + ':' + ((t % 60) / 100).toFixed(2).slice(-2)
return t
}
// 时间格式化
// 监听 歌曲播放时间
watchAudio: function () {
this.audioCtx.onTimeUpdate(() => {
// 拿取当前音频的总时长 带小数 转换成整数 放在slider:max="{{curduration}}"
this.data.curduration = Math.ceil(this.audioCtx.duration);
// 将音频总时长 换成"04:18" 格式显示
this.data.duration = format(this.data.curduration);
// 拿取当前音频的播放时间 slider: value="{{curSecond}}"
this.data.curSecond = Math.ceil(this.audioCtx.currentTime);
// 将当前音频的播放时间 换成"04:18" 格式显示
this.data.currentSecond = format(this.audioCtx.currentTime);
// 将拿到的 数据 赋值给 显示的数据
this.setData({
curduration: this.data.curduration,
duration: this.data.duration,
curSecond: this.data.curSecond,
currentSecond: this.data.currentSecond
})
// 自动切换下一首歌曲
if(this.data.curduration === this.data.curSecond && this.data.loop===false){
this.nextplay()
}
})
},
// 在 onLoad 函数中调用 监听事件函数 当音频事件 发生改变 触发
onLoad(options) {
this.audioCtx = wx.createInnerAudioContext()
this.watchAudio()
},
css
.slide {
margin-left: 80rpx;
margin-right: 80rpx;
color: black;
opacity: 0.8;
height: 25vh;
}
.slide .slide_text {
display: flex;
margin: 50rpx;
justify-content: space-between;
}
// 渐变播放条背景色
.color {
opacity: 0.6;
background: rgb(238, 174, 202);
background: radial-gradient(circle, rgba(238, 174, 202, 1) 0%, rgba(148, 187, 233, 1) 100%);
}
footer
footer中主要就是各种按钮的操作。 主要采取了{{}} 动态绑定 以及 ?: 进行操作选择 其中wx.showActionSheet)是小程序自带的弹窗方法。
wxml
<view class="footer">
<image bindtap="loop" src="{{loop ? 'https://gitee.com/unfortunately-there-is-no-if/img/raw/master/Image/%E5%8D%95%E6%9B%B2%E5%BE%AA%E7%8E%AF.png':'https://gitee.com/unfortunately-there-is-no-if/img/raw/master/Image/%E5%9C%86%E5%BE%AA%E7%8E%AF%E6%92%AD%E6%94%BE.png'}}" mode="" />
<image bindtap="lastplay" src="https://gitee.com/unfortunately-there-is-no-if/img/raw/master/Image/%E4%B8%8A%E4%B8%80%E9%A6%96.png" mode="" />
<image bindtap="{{show ? 'pause':'play'}}" src="{{show ? 'https://gitee.com/unfortunately-there-is-no-if/img/raw/master/Image/Player,%20pause.png' :'https://gitee.com/unfortunately-there-is-no-if/img/raw/master/Image/%E6%92%AD%E6%94%BE2%20(1).png'}}" mode="" />
<image bindtap="nextplay" src="https://gitee.com/unfortunately-there-is-no-if/img/raw/master/Image/%E4%B8%8B%E4%B8%80%E9%A6%96.png" mode="" />
<image bindtap="showActionSheet" src="https://gitee.com/unfortunately-there-is-no-if/img/raw/master/Image/24gf-playlist.png" mode="" />
</view>
js
// 播放功能
play: function () {
if (this.data.show == false) {
this.setData({
// 切换 播放 和 暂停 按钮
show: !this.data.show
})
}
this.audioCtx.play()
},
// 暂停功能
pause: function () {
this.setData({
show: !this.data.show
}),
this.audioCtx.pause()
},
// 循环播放功能
loop: function () {
this.setData({
loop: !this.data.loop
})
this.audioCtx.loop = this.data.loop
},
// 切换上一首歌曲
lastplay: function () {
// 定义一个歌曲数组
let array = this.data.songs;
let name = this.data.name;
// 通过遍历 数组 判断歌曲名 来拿到当前播放歌曲的信息
for (let i = 0; i < array.length; i++) {
if (name === array[i].name) {
// 改变下标 因为最开始一首歌 没有上一首,所以我们把整个歌曲 串起来
//跳转最后一首歌
let num = (i + array.length - 1) % array.length
// 重新赋值
this.setData({
src: this.data.songs[num].src,
name: this.data.songs[num].name,
author: this.data.songs[num].author,
poster: this.data.songs[num].poster
})
}
}
// 重新定义当前播放歌曲地址
this.audioCtx.src = this.data.src
this.audioCtx.play()
if (this.data.show == false)
this.setData({
show: !this.data.show
})
},
// 与切换上一首功能大致相同
nextplay: function () {
let array = this.data.songs;
let name = this.data.name;
for (let i = 0; i < array.length; i++) {
if (name === array[i].name) {
let num = (i + array.length + 1) % array.length
this.setData({
src: this.data.songs[num].src,
name: this.data.songs[num].name,
author: this.data.songs[num].author,
poster: this.data.songs[num].poster
})
}
}
this.audioCtx.src = this.data.src
this.play()
if (this.data.show == false)
this.setData({
show: !this.data.show
})
},
// 微信小程序 自带的一个 展示框
showActionSheet: function () {
let array = this.data.songs;
let res = new Array();
// 定义一个只存有歌曲名的数组
for (let i = 0; i < array.length; i++) {
res.push(array[i].name)
}
wx.showActionSheet({
itemList: res,
alertText: '播放列表',
itemColor: '#157898',
success: (result) => {
// wx.showActionSheet 中 tapIndex 即拿取的当前歌曲的序号
// 拿取点击的 歌曲的 属性 并修改值
this.setData({
src: array[result.tapIndex].src,
name: array[result.tapIndex].name,
author: array[result.tapIndex].author,
poster: array[result.tapIndex].poster
})
// 歌曲重新赋 地址
this.audioCtx.src = this.data.src
this.audioCtx.play()
if (this.data.show == false)
this.setData({
show: !this.data.show
})
},
fail: (res) => {
console.log(res)
},
complete: (res) => {},
})
}
css
.footer {
opacity: 1;
background-color: black;
height: 10vh;
width: 100%;
display: flex;
justify-content: center;
align-items: center
}
.footer image {
padding: 30rpx;
width: 70rpx;
height: 70rpx;
}
.footer .image {
width: 90rpx;
height: 90rpx;
}