仿网易云音乐的小程序项目(粗糙版)
前言
通过 github 拉取下的后台服务器,根据接口文档,调用真实的网易云音乐数据,独立开发而成,收获很多 完成了 :
- 每日推荐的歌曲,歌单,视频页面,账号登陆的展示数据信息功能
- 歌曲和视频的播放切换,进度条实时跟进功能
- 视频区下拉刷新,模糊搜索歌曲功能
开发完成后最大的感受就是一定要会看,会查微信小程序的官方 API 文档,出 bug 时首先检查语法是否正确(刚入门比较容易犯拼写错误),根据控制台给错的错误信息定位到相应文件,进行逻辑检查,注意后端返回的数据类型和和格式转换,看清接口文档明白后端要接收到的实际参数!!
页面预览:
功能介绍:获取诸如每日推荐的静态数据
诸如上述的每日轮播图,歌曲,歌单数据,获取方式十分简单,只需要参考调用文档,发送 GET 数据请求,解析收到的数据,将需要的信息展示到预先设好的图片插槽上即可。
在相应模块中的 js 文件里设置存放诸如每日轮播图,歌曲,歌单数据的数组
1 | data: { |
然后在 onLoad 声明周期函数中,调用接口(注意文档所要求携带的参数),获取数据,将接口返回的数据通过 setData 方法保存至刚设置的数组里。
1 | onLoad: async function (options) { |
最后通过插槽展示出来,对于一些组件,可以添加官方给出的样式属性,让布局的交互效果更佳。
1 | <!-- 轮播图区域 添加的属性:自动播放,切换时间间隔,下方小圆点 --> |
推荐歌曲部分大抵如此:
1 | <!-- 推荐歌曲内容区 --> |
榜单部分:这里以 item 项进行滑动,每个 item 项宽度固定,只需调节宽度大小即可控制下一 item 项在页面的显示位置。
1 | <swiper class="topListSwiper" circular next-margin="110rpx"> |
功能介绍:用户登陆
整个流程大致为:
- 前端验证
- 实时收集用户输入框数据(给账密绑定 id ,通过事件委托实时存储用户输入值)
- 验证账号密码是否合法(验证是否为空,格式正确,长度这几种情况)
- 验证通过就将账密发个请求给客户端,反之不通过则提示用户,不用发请求
- 后端验证
- 调用数据库验证用户是否存在
- 用户存在即验证密码是否正确,跟下一步一样,账密任何一个输错停止下一步,并告诉前端**错误
- 都正确就返回用户的数据给前端,并提示登陆成功
在实时存储和验证用户输入的账号密码时用的是同一个处理函数,怎么却分他们谁是谁呢,一般用 id 进行区别。这里有个重要知识点:事件委托。事件委托就是把子元素的事件让父元素完成,可通过event.target
找到触发的子元素,currentTarget
要求绑定事件的元素一定是触发事件的元素。
根据 id 存储信息
1 | let type = event.currentTarget.dataset.type |
点击登陆,携带账号密码信息触发后端验证(状态码为:200 通过,400 手机号错误,502 密码错误),wx.showToast
显示登陆情况。成功后 wx.setStorageSync
将用户信息保存到本地(存储为 json 字符串格式类型),接着 reLaunch
重定向到某页面。
1 | wx.setStorageSync('userInfo', JSON.stringify(result.profile)) |
1 | wx.reLaunch({ |
功能介绍:视频播放
网易获取视频需要用户登陆,用户登陆后把保存在本地的 cookies 取出。因为项目是做成上边导航栏,下边根据导航栏的索引 id 获取相关视频的开发模式,所以先获取导航栏的数据,和标签 id 分别以数组的形式存放在 data 数据中。
获取导航栏数据方式跟先前拉取静态歌曲方式一样,均通过异步请求,来获取保存显示数据。不过这里有些不同,官方给的导航 api 中,标签 id 不能以数字开头,所以在显示时,均加以 ”scroll“ 字符串来满足要求,
1 | <!-- 视频导航栏区域 --> |
默认是第一个标签页视频,在点击第一个导航栏标签时,会将上面的字符串更改为数值类型,同时初始化该变迁下的存储视频数组,接着携带 id 触发获取视频调用函数。因为获取的视频里,单个视频没有唯一的 id,所以我需要用 map 加工一下
1 | async getVideoList (navId) { |
到这里,视频获取就算完成额了,但是存在一些小问题。比如:同时播放多个视频。不能对视频进行上一次播放进度的跟踪。
解决多个视频同时播放的问题(单例模式)和回到上个视频的播放进度
单例模式主要思想:在多个对象的场景下,通过一个变量接收,始终保持只有一个对象。那么用户在播放第二个视频时,我们需要通过上述 map 生成的唯一 id 判断这俩者不是同一个视频。
回到上个视频的播放进度的整体思路是:当我们每点击一个视频,存储该视频 id,创建一个事件对象,
1 | let vid = event.currentTarget.id |
查看他是否有播放记录,有就跳转到指定位置,没有就重头开始播放,然后实时将记录存储到刚创建的对象里的记录播放时长属性中。
1 | // 判断当前的视频之前是否播放过,是否有播放记录, 如果有,跳转至指定的播放位置 |
关于播放时长的回调,播放前需判断播放时长是否有值,有就覆盖,在播放。
1 | if (videoItem) { |
再有就是视频播放完后,需要善删掉视频播放状态
1 | videoUpdateTime.splice( |
下拉刷新,重新根据标签 id 发数据请求:上拉同理
1 | handleRefresher () { |
功能介绍:音乐播放
界面预览:
上面的日期采用 date.getMonth()
来定义进行实时显示,歌曲部分除付费歌曲外,都能通过接口调用获取。获取前需要验证用户是否登陆,通过先前登陆存储在本地的 userInfo
进行判断,没登陆即 reLaunch
到 login 页面。
这里稍提一下图像文本居中排布问题:
例如左侧图片,右侧文字需要做到居中对其、文本框内图标,与默认文字对其的情况。
根据子绝父相设置定位,接着父子元素都要设置 flex
弹性布局。每首歌曲是一个 item 项目,给图片设置宽高圆角底边距后(不用设浮动),右侧的文字也将看作是一个 item,设置 flex,和经典居中三命令执行。
1 | .musicInfo text { |
最右侧图标利用绝对定位加边距调整即可;图标在搜索框中与文字齐平,图标可用 vertical-align
来设置 center 值。
居中排版问题说完,继续说音乐播放相关的事。
音乐暂停播放键的显示:
对暂停还是播放的图标有俩个类名来取决显示那个图标,默认暂停(isplay:false),设置 isPlay 属性,没点一次通过 setData()
对 isplay 进行取反更新操作。
点击歌曲后跳转到播放页面获取详情信息:
点击歌曲后,需要携带所点击的歌曲的名称,id 信息发送到播放页面
1 | url: '/songPackage/pages/songDetail/songDetail?musicId=' + song.id |
播放页面那边接受 id 发送请求获取该歌曲详情信息,其中 durationTime 为已播放时长。
1 | // 获取音乐功能详情的功能函数 |
播放或暂停音乐:
播放一首歌曲,需要知道该播放或暂停键的 isPlay 状态,音乐 id,和该音乐的链接地址,音乐的链接地址可通过 id 向接口发送请求获取。
小问题:当播放一首歌退出界面在点进来,会重头播放:
这时只需判断全局下的播放状态为真和 id 等于现在点的这首歌 id 就改变播放按钮状态即可,相同则不做播放处理。
1 | if ( |
上一首或下一首切换:
给上一首或下一首分别绑定 id 值,但用统一处理事件,用户点击时连同 id 值一起传入,通过播放事件判断是上一首还是下一首,上一首的话就把当前歌曲 id 所在每日推荐的位置减一进行播放,反之+1。
点击的同时需要暂停当前播放曲目,
1 | handleSwitch (event) { |
1 | PubSub.subscribe('switchType', (msg, type) => { |
遇到的问题
导航元素 id 类型转换问题:
let navId = event.currentTarget.id;
再点击导航栏获取元素 id,想给他动态添加下标样式时,发现不成功,打印 id 发现拿到的元素是字符型的,而数据项中的 id 应该是数值,所以索引不到对应的 item 元素项。
解决方法:在更新数据时,把 id*1 转成数值型就好,或者位移运算符>>> 0,或者在获取方式改为:let navId = event.currentTarget.dataset.id
视频播放:
每点击一个视频就通过 createVideoContext 创建一个事件对象,在 data 中保存视频的数据列表,id,还有记录视频的播放时长 videoUpdateTime。点击的时候,更新 data 中的视频 id,接着监听进度条(videoUpdateTime 以对象的形式存视频 id 和进度时间,如果查询到有这个视频 id 的记录,就 seek,没有就 push 添加记录,然后 play 播放)判断视频是否播放过,来决定是否跳转(videoContext.seek)到相应进度条。
下拉刷新:
在 handleRefresher 生命周期中重新发起一个该导航栏下的数据请求即可。
视频第一帧:
给 video 加上poster="{{item.data.coverUrl}}"
属性,image 部分加载,通过判断上一个视频播放的 id 和当前视频 id 是否一致决定加载的是视频还是图片。点击图片触发的播放回调函数跟视频的一致。
做的优化
问题:点击歌曲的时候,不知道歌曲有无请求过,每次都是发送请求播放。
优化内容:将歌曲地址进行存储,播放时判断有无链接,有就通过backgroundAudioManager.src= musicLink
调用进行播放,没有就重新发送,减少后台请求次数。
还会继续开发,计划完善
- 各类歌单功能:点击歌单,跳转到歌单列表,再点击歌曲,进行播放
- 显示歌曲的歌词,应该有现成的库可直接调用
- 显示歌曲评论,完成歌曲的交互功能
- 根据搜索名称得到单曲,歌单,mv 之类的数据
呃呃呃,,,差不多这样