云开发练手项目:音乐播放器(上)

本文目录:

  • 1.云开发的优势
  • 2.云开发的五大基础能力
  • 3.基本结构分析:
  • 4.写轮播图
  • 5.组件化开发流程
  • 6.数据监听器observers
  • 7.异步操作解决方案
  • 8.小程序中怎样使用async函数
  • 9.第一个云函数getPlaylist
  • 10.小程序端调用云函数
  • 11.云函数获取数据库中的大于100条的数据
  • 12.小程序端调用云函数
  • 13.tcb-router
  • 14.本地数据存储
  • 15.音乐播放的控制
  • 16.如何实现组件间传值
  • 17.给小程序设置全局属性和方法

1.云开发的优势

正常的开发分为前端和后端
传统的小程序开发完成之后需要进行一个上线部署,而传统部署的基本步骤有:购买服务器、域名,备案,网络防护,负载均衡,监控警告等。这些事情非常的繁琐,让人头疼。


传统模式.png

小程序云开发,弱化了后端和运维的工作,不需要搭建服务器。
云开发赋予了开发者稳定,安全的读取数据,上传文件的能力。


云开发模式.png

serverless=》无服务开发=》小程序的云开发

在云开发的核心理念中,函数即服务,依托腾讯端提供的后端服务,我们通过函数就可以实现调用,从而实现serverless

2.云开发的五大基础能力

  • 1.云函数:在云端运行代码,并且具有天然的鉴权机制。
  • 2.云数据库:既可以在云函数端操作,又可以在小程序端操作的非关系型json数据库(类似moongodb)
  • 3.云调用:基于云函数免鉴权使用小程序开放接口的能力
  • 4.HTTP API:可以让第三方服务很方便的在已有服务器上访问云资源,实现与云开发的互通。
  • 5.云存储:在云端存储文件,可以在云端控制台可视化管理

我们可以通过云函数去定期的去第三方数据服务器拿数据,然后更新到云数据库中

1.什么是小程序的云开发?

传统模式:小程序端展现的数据是发请求给后端拿到的

云开发模式:小程序端提供的原生接口可以直接去操作远程的云数据库,云函数,云存储。而我们根本根本不知道后端部署在那里。

2.什么是serverless?

打破前端和后端的物理隔离

当我们使用后端服务的时候,不需要关注后端的ip地址是什么等等

在小程序官网上注册账号,然后下载开发者工具,打开开发者工具,创建一个新项目。
需要注意一点,APPID是每一个小程序的唯一标识,这个ID在官网账号中的“开发”界面可以查看到。
进入项目之后,第一次使用云开发的用户需要点击界面上方的“云开发”按钮去开通服务。
首先可以选择创建一个test作为开发环境的云服务,等到项目上线再创建一个生产环境进行使用。

3.基本结构分析:

cloudfunctions=》云函数部分

miniprogram=》前端部分代码

  • images图片资源
  • pages创建小程序的时候自带文件夹和文件(可以全部删除)
  • style创建小程序的时候自带样式(可以全部删除)
项目文件结构.png

app.js全局js文件
onLaunch:function(){}
当小程序启动的时候触发的钩子函数
wx.cloud.init({ env:"在此处填入环境ID",
这个地方填入的是哪个ID,小程序自动连接的就是对应的环境,先填开发环境,上线的时候把这个地方改成生产环境就可以了。
traceUser: true
设置为true的时候,每一个访问过我们的小程序的用户都会被记录,并且以倒序的顺序进行显示
})

app.json全局配置文件

pages
//文件的路径
window
//窗口的一些配置(页面的最上方)
“sitemapLocation”:sitemap.json
//小程序开放的内部搜索,对应的配置文件sitemap.json,决定了我们的小程序界面是否能被搜索到,在小程序优化中可以用到
tabBar
//小程序封装的一个对象,有color,selectedColor,list等常用属性。
//对应的小程序页面下方的导航,最少两个,最多五个

app.wxss全局样式
README.md小程序的开发说明
project.config小程序项目的配置文件,突出为整个项目
iconfont下载图标很方便,可以自由选择格式,大小,以及颜色(点击下载,直接下载到本地就可以直接使用了)

把style文件夹中的guide.wxss和app.wxss中的小程序自带样式都去除掉,样式都是我们自定义
写最初的几个主页面:
先在app.json中的“pages”中把框架自带的页面全部删除,然后加上自己要写的几个页面路径,保存之后在对应的路径就会自动生成页面文件。(手动把框架自带的页面文件删除掉)

4.写轮播图

小程序原生自带swiper组件,里面的项目swiper-item,在swiper-item里放image标签
小程序自带的block标签,建议把wx:for写在block上面,block不会真实渲染,在轮播图这里我们在swiper-item外面包裹一层block,然后wx:for渲染写在block标签上

swiper的常用属性:

  • indicator-dots="true",显示导航的小点,默认为flase
  • autoplay=“true”自动播放
  • interval=“2000”自动播放的间隔是2000ms
  • duration=”1000“滑动播放时长为1000ms

小程序自带的image标签的常用属性

  • mode=”scaleToFill“,保证图片完全覆盖当前image容器,这种缩放模式下图片非常有可能会产生变形,实际效果不好
  • mode=”aspectFit“,让图片能够完整的显示在容器中,缺点是有可能会让容器留白
  • mode=”widthFix“,让图片能够完全覆盖容器,同时保持图片的宽高比不变,同时给image标签增加width100%height100%的样式,保证image能够完整覆盖父元素。这个缩放模式是实际项目中最常用的。

5.组件化开发流程

组件:在用户界面开发领域,组件是一种面向用户的,独立的,可复用的交互元素的封装。

组件的组成:
结构=》wxml
逻辑=》js
样式=》wxss

组件的设计原理:高内聚,低耦合,单一职责,避免过多参数
封装第一个组件:歌曲列表组件
1.创建
首先在项目的miniprogram文件夹下新建一个文件夹components,然后在components中右键创建对应的组件(这里是playlist)
2.在页面中引用和使用
在页面的json中进行引用

{
  "usingComponents": {
    "x-playlist": "/components/playlist/playlist"
  },
  "enablePullDownRefresh":true
}

在页面的wxml中进行使用

3.传递数据,这里想把页面中的歌曲数据playlist传递给组件


  
    
  

4.组件接收页面传递过来的数据
在组件的js文件中使用properties进行接收(需要指定所接收到的数据的类型,不指定的话会报错)

  properties: {
    // 接收父组件传递的参数,并规定参数的类型
    playlist: {
      type: Object
    }
  },

5.组件在wxml就可以使用接收到的数据(渲染数据)

在小程序中的背景图片只能使用本地图片,不允许使用网络图
如何把小图片转换成base64?在百度上搜索“在线制作base64”,很多网站都可以转换

6.数据监听器observers

监听对象下的属性,和接受父组件传递参数的properties在同一级。
['对象.属性']
监听到的值不能直接用this.setData赋给对象的属性本身,这样会陷入数据监听死循环
解决方法:在组件的内部重新定义一个数据进行赋值。

  // 数据监听器
  observers: {
    // 监听对象下面的属性
    ['playlist.playCount'](count) {
      this.setData({
        _count: this._tranNumber(count, 2)
      })
    }
  },

巧妙的去除数字后面的小数点
let numStr = num.toString().split('.')[0]
小程序对于wx:for循环提供了一个wx:key=“*this”,其中*this代表的就是元素本身=====>对于循环的纯数组而言,如果循环的是对象数组,则可以直接绑定对象中的唯一属性,如id,在不写别名的情况下,小程序会自动识别循环的每个对象下的id属性,并且进行绑定。
注意:在循环出来的对象不会动态变化的情况下,key值可以绑定的随意些,否则必须要绑定足够有辨识度的唯一标识,否则小程序无法识别元素的动态变化。

7.异步操作解决方案

传统的回调地狱式异步编程写法:

setTimeout(() => {
  console.log(1)
  setTimeout(() => {
    console.log(2)
    setTimeout(() => {
      console.log(3)
    }, 3000);
  }, 2000);
}, 1000);

promise是es6的异步操作解决方案,字面意思就是承诺,promise有三个状态,pending代表等待,fulfilled代表成功,rejected代表失败,状态一旦改变,则无法回退。
上面的异步操作的promise版本写法

new Promise((resolve,reject)=>{
  setTimeout(() => {
    console.log(1)
    resolve()
  }, 1000);
}).then((res)=>{
  setTimeout(() => {
    console.log(2)
    resolve()
  }, 2000);
}).then((res)=>{
  setTimeout(() => {
    console.log(3)
    resolve()
  }, 3000);
})

8.小程序中怎样使用async函数

es7的异步操作解决方案:async和await
云函数默认支持es7语法,但是小程序开发环境还不行,所以要想在小程序端欢快的使用es7语法,则首先需要解决环境问题。
把 regenerator/runtime.js 文件引用到有使用 async/await 的文件当中。
import regeneratorRuntime from '../../utils/runtime.js'
注意:regeneratorRuntime必须叫这个名字,不能自定义
普通函数没有写renturn则没有返回值,而async函数的返回值是一个promise对象

onLoad:function(options){
    this.foo()
}
async foo(){
    console.log('foo')
    //await一定要async函数里面才能发挥正常作用
    let res = await this.timeout()
    console.log(res)
},
timeout(){
    return new Promise((resolve,reject) => {
        setTimeout(()=>{
            console.log(1)
            resolve('resolved')
        },1000)
    })
}

执行结果
foo
1
resolved

9.第一个云函数getPlaylist

getPlaylist这个云函数我们首先需要安装三个依赖,发请求拿数据用的
在cloudfunctions文件夹中新建一个getPlaylist文件夹,然后在这个文件夹下打开终端命令行工具,输入npm init -y将getPlaylist初始化为一个npm管理下的项目,然后一次安装下面这三个依赖

npm install --save request
npm install --save request-promise
npm install --save wx-server-sdk@latest

在getPlaylist文件夹下引入
const rp = require('request-promise')
然后开始写发请求的代码

exports.main = async(event, context) => {
  const playlist = await rp(URL).then((res) => {
    return JSON.parse(res).result
  })
}

如果要在云函数中打印一些数据用来调试,但是这个打印信息不会显示在调试器中,因为调试器属于前端工具,而云函数属于后端部分的代码。所以我们可以先上传并部署:云端安装依赖(不上传node_modules)
云函数调试位置:云开发=》云函数=》云端测试,当前这个请求不需要参数,把默认的参数清空,然后“运行测试”就可以在下面看到测试的结果了。
返回结果是null是因为当前的getPlaylist云函数并没有写返回值。
注意:在云函数中的任何一处修改要想生效 都需要进行上传和部署。

至此,我们已经在云函数中拿到了想要的歌单信息数据playlist ,接下来要把这个数据插入到数据库中,首先需要在数据库中“创建集合”,集合名称定义为playlist,往数据库中插入数据只能一条一条的插入。

10.云函数往数据库插入不重复的数据

给数据库插入信息之前需要先在getPlaylist云函数中初始化数据库
const db = cloud.database()
接下来调用数据库,往playlist集合中插入数据,并且同时插入一个cerateTime字段,记录数据产生的时间。
serverDate获取当前服务器的时间

  for (let i = 0,len = playlist; i < len; i++) {
    await db.collection('playlist').add({
      data: {
        ...playlist[i],
        createTime: db.serverDate(),
      }
    }).then((res) => {
      console.log('插入成功')
    }).catch((err) => {
      console.error('插入失败')
    })
  }

上面这段代码有一个明显的问题,那就是当多次读取数据的时候,重复的歌单信息就会被多次添加,所以每次读取歌单信息都应该和数据库当前已有的歌单信息进行对比,相同的信息不会被重复添加。

首先定义一个list,先获取歌单信息已有的信息,存储在list变量中
const list = await db.collection('playlist').get()
然后将list和playlist对比去重,将不重复的数据放置到newData变量中。
定义一个flag,true代表默认的“不重复”

  const newData = []
  for (let i = 0, len1 = playlist.length; i < len1; i++) {
    let flag = true
    for (let j = 0, len2 = list.data.length; j < len2; j++) {
      if (playlist[i].id === list.data[j].id) {
        flag = false
        break
      }
    }
    if (flag) {
      newData.push(playlist[i])
    }
  }

这样一开始插入的数据也要从playlist变为现在的不重复数据形成的数组newData

11.云函数获取数据库中的大于100条的数据

现在还遗留一个问题就是在云函数中获取数据中的信息,只能获取100条,在小程序代码中最多只能获取到20条。所以现在我们需要突破100条这个限制。
解决思路:假如有210条数据,分三次请求,最后再把这三次请求拿到的数据进行汇总,就可以获得全部的数据。
全部数据list不能通过const list = await db.collection('playlist').get()简单获得,需要进行下面的优化:
首先需要获得当前数据总的条数
const countResult = await db.collection('playlist').count()
countResult拿到的是一个对象,其中的total属性对应的数据的数量。
·const total = countResult.total·
定义每次取数据的数量
const MAX_LIMIT = 100
求出应该取几次数据
const batchTimes = Math.ceil(total / MAX_LIMIT)
需要等待几次拿数据的请求完成完成后才能拼装出真正完整的数据。

  const tasks = []
  for (let i = 0; i < batchTimes; i++) {
    let promise = db.collection('playlist').skip(i * MAX_LIMIT).limit(MAX_LIMIT).get()
    tasks.push(promise)
  }
  let list = {
    data: []
  }
  if (tasks.length > 0) {
    list = (await Promise.all(tasks)).reduce((acc, cur) => {
      return {
        data: acc.data.concat(cur.data)
      }
    })

  }

刚才所写的从指定的音乐接口拿数据,然后将数据插入数据库的操作,我们希望能够在云函数中定时自动触发。
需要在对应的云函数文件夹中新建一个config.json文件

配置好之后一定要右键云函数文件夹,“上传触发器”,这样触发器才能生效。
config.json文件名是规定好的,不能改。myTrigger是自定义的触发器名称。

{
  "triggers":[
    {
      "name":"myTrigger",
      "type":"timer",
      "config":"0 0 10,14,16,23 * * * *"
    }
  ]
}

12.小程序端调用云函数

接下来就是小程序端读取数据,并且把数据渲染到页面上。
我们新创建一个music云函数“新建Nodejs云函数”,然后在这个music云函数中写调用数据库,获取数据的逻辑代码。
skip和limit方便我们获取指定条数的数据以及进行分页。
orderBy表示排序,第一个参数是排序依据的字段名称,第二个参数‘desc’代表逆序。

return await cloud.database()
    .collection('playlist')
    .skip(event.start).limit(event.count)
    .orderBy('createTime','desc')
    .get().then((res)=>{return res})

接下来在小程序界面的js文件中请求云函数music,取第0-15条数据

wx.cloud.callFunction({
   name:'music',
   data:{
     start:0,
     count:15
   }
}).then((res) => {
      this.setData({
        playlist: this.data.playlist.concat(res.result.data)
      })
      wx.stopPullDownRefresh()
    })

如果实现触底下拉,请求更多15条的歌单信息?
微信自带onReachBottom属性,监听页面触底,我们把刚才请求歌单的方法进行一下优化,并封装在_getPlaylist方法中,当触底时自动触发,并将请求的新数据拼接给playlist鼻变量。

  _getPlaylist() {
    wx.showLoading({
      title: '加载中',
    })
    wx.cloud.callFunction({
      name: 'music',
      data: {
        start: this.data.playlist.length,
        count: MAX_LIMIT,
        $url:'playlist'
      }
    }).then((res) => {
      this.setData({
        playlist: this.data.playlist.concat(res.result.data)
      })
      wx.stopPullDownRefresh()
      wx.hideLoading()
    })
  }

调用云函数的data参数中的$url:'playlist'代表要调用的对应云函数中对应的router
当用户下拉整个页面的时候,怎么实现页面刷新,并重置当前已加载的歌单信息数据?
在当前页面的json文件中增加属性"enablePullDownRefresh":true,这代表允许当前页面下拉刷新,同时在页面的js文件中有一个onPullDownRefresh属性,是监听用户下拉动作的

  onPullDownRefresh: function() {
    this.setData({
      playlist:[]
    })
    this._getPlaylist()
  },

微信暂时无法知道用户的下拉动作是什么时候结束的,所以可以在请求数据结束的时候增加wx.stopPullDownRefresh()用来停止下拉刷新的动画。

13.tcb-router

一个用户在一个云环境中只能创建50个云函数
相似的请求归类到同一个云函数处理
tcb-router是一个koa风格的小程序云开发云函数轻量级类路由库,主要用于优化服务端函数处理逻辑。
在对应的云函数中进行安装
npm install --save tcb-router
在对应的云函数的js文件中引用
const TcbRouter = require('tcb-router')
在云函数的js文件中的入口函数中进行使用,app可以创建当前TcbRouter服务,这样TcbRouter就会自动的处理event参数和路由转发,在结束的时候别忘了通过app.serve()把当前的服务返回。
···
exports.main = async(event, context) => {
const app = new TcbRouter({
event
})
return app.serve()
}
···
用ctx.body把数据返回给小程序端

  app.router('playlist', async(ctx, next) => {
    ctx.body = await cloud.database().collection('playlist')
      .skip(event.start)
      .limit(event.count)
      .orderBy('creatTime', 'desc')
      .get()
      .then((res) => {
        return res
      })
  })

小程序端在调用云函数时,还要在data中增加一个属性
`$url:'xxx``
xxx对应的是name对应云函数中详细的路由。

页面之间的跳转

wx.navigateTo({
    url:`../../pages/musiclist/musiclist?playlistid=${this.properties.playlist.id}`,
})

上面的properties写成data貌似也不会有什么问题。
这里问号后面的动态路由是要告诉musiclist页面我要进入的是哪一个歌单,
musiclist页面的onload生命周期函数中的options就可以获取到这个传递过来的数据,然后依据这个id去调用云函数,获取对应歌单的歌曲列表。

  onLoad: function(options) {
    console.log(options)
    wx.showLoading({
      title: '加载中',
    })
    wx.cloud.callFunction({
      name: 'music',
      data: {
        playlistid: options.playlistid,
        $url: 'musiclist'
      }
    }).then((res) => {
      console.log(res)
      const pl = res.result.playlist
      this.setData({
        musiclist: pl.tracks,
        listInfo: {
          coverImgUrl: pl.coverImgUrl,
          name: pl.name,
        }
      })
      this._setMuscilist()
      wx.hideLoading()
    })
  },

当小程序开发的页面层级比较多的时候,每次保存都会让小程序从主页开始,这样很不方便,可以在上方的编译模式中新建一个编译模式,启动页面定位为想要的页面,另外比如有启动参数,则需要填写上,如playlistId = 28171112148

实现循环的歌曲列表,当前点击的歌曲动态添加playing高亮样式。
在小程序中,所有自定义的属性都用data-开头


  
  

实现原理:如果当前点击事件触发的歌曲id,和自定义属性musicid是相等的,则可以判定当前歌曲为用户点击的。
点击触发的onSelect事件,当前点击事件的event参数有两个属性,一个是target,另一个是currentTarget,而绑定的自定义属性是在currentTarget上面。原因如下:
关于事件的几个要素:

  • 事件源,触发事件的真正的元素
  • 事件处理函数
  • 事件对象,事件处理函数的默认参数event,event中有target属性和currentTarget属性,target对应的是事件源,currentTarget指的是绑定事件的元素
  • 事件类型
    onSelect(event) {
      const musicid = event.currentTarget.dataset.musicid
      this.setData({
        playingId: musicid
      })
      wx.navigateTo({
        url: `../../pages/player/player?musicid=${musicid}&index=${event.currentTarget.dataset.index}`,
      })
    }

小程序的自带组件就是一些标签,我们通过给标签配置不同的属性,就可以实现不同的效果

组件中的properties和data都是用来定义组件数据的,它们的差别:
properties:调用方传给给组件的
data:组件内部使用的数据

14.本地数据存储

将数据保存到storage中

wx.setStorage({
  key: 'musiclist',
  data: this.data.musiclist,
})

对于不需要进行页面操作和显示的数据,我们可以不定义在data中,直接定义一个全局变量就行,这样的话进行赋值也会更加的方便。
let musiclist = []
运用同步方法去给这个变量赋值,因为获取到值需要直接进行下一步的逻辑处理
musiclist = wx.getStorageSync('musiclist')

动态的设置页面上方的导航标题

wx.setNavigationBarTitle({
  title: music.name,
})

给容器动态绑定一个铺满全部的背景图片

在项目中使用iconfont
进入官网iconfont.cn,将想要的图标点击购物车图标=>加入购物车
新建项目=>加入项目,比如:demo,点击fontclass =>点击查看在线链接,生成代码,会生成一个css文件的链接地址,可以下载到本地,然后将css文件修改成wxss文件(也可以不下载,直接拷贝链接中的代码放入项目中)

此时图标wxss文件是放在项目的根目录下的,我们要在app.wxss文件中进行引用,然后在项目中就可以通过class进行使用。
@import "iconfont.wxss";

15.音乐播放的控制

小程序提供了一个wx.getBackgroundAudioManager()方法用来控制唯一背景音乐的播放,在要播放背景音乐的页面的js文件首先定义一个变量,去获取全局唯一的背景音频管理器
const backgroundAudioManager = wx.getBackgroundAudioManager()
然后通过给backgroundAudioManager 的src属性赋值,就可以实现背景音乐的额播放,同时注意,还需要同时设置title,否则会报错

backgroundAudioManager.src = JSON.parse(res.result).data[0].url
backgroundAudioManager.title = music.name

如果需要在任何界面都可以听到这个背景音乐,则需要在app.json中配置(和pages同级)

"requireBackgroundModes":[
    "audio"
],

同时还可以通过为页面下方的mini播放器设置图片、歌手和专辑名称

backgroundAudioManager.coverImgUrl = music.al.picUrl
backgroundAudioManager.singer = music.ar[0].name
backgroundAudioManager.epname = music.al.name

背景音乐的暂停
backgroundAudioManager.pause()
背景音乐的播放
backgroundAudioManager.play()

在css中,animation-play-state: paused;可以让动画停在当前那一帧,只需要动态的给做动画的元素添加上这个属性,就可以实现动画播放的开始与暂停

backgroundAudioManager.duration=>获取当前背景音乐的时长,但是有时候获取到的是underfined,解决办法:

if(backgroundAudioManager.duration == undefined)
    //上面这样样判定是不合理的,因为null==undefined也会是true
if(typepf backgroundAudioManager.duration != 'undefined')
    //应该像上面那样判定

怎么动态的给data中的对象中某一个属性赋值

this.setData({
    ['object.xxx']:'yyyyyy'
})

this.data.progress这样给data中的数据赋值可以成功,但不会自动响应到页面上

backgroundAudioManager有一些事件,我们需要在这些事件上绑定对应的回调函数,如:
背景音乐可以播放的时候:backgroundAudioManager.onPlay
backgroundAudioManager.seek()=》重新定义当前背景音乐的正在播放的时间点,参数为要跳转的秒

子组件激活父元素的事件
this.triggerEvent('musicEnd')
父组件在调用子组件的标签中进行接收,同时接收到响应后去触发自身的onNext事件

进度条的拖拽事件和backgroundAudioManager.onTimeUpdate事件是不能同时进行的,否则会造成拖拽的时候进度条会一直闪的画面,这里的解决办法是设置一个锁:isMoving
当拖拽开始的时候isMoving = true
拖拽结束的时候isMoving = false
当isMoving = false的时候,onTimeUpdate里面的代码才去执行。

小程序控制组件的显示与隐藏
hidden="{{flag}}" //flag为true时隐藏,为false显示

接收父组件传递过来的数据,如果接收的数据,这个数据除了类型,还有其他的属性,则需要写成对象的形式,如果只需要声明一个类型,则可以不用对象的形式

  properties: {
    isLyricShow: {
      type: Boolean,
      value: false
    },
    lyric: String
  },

16.如何实现组件间传值

自组件给父组件传值:
自组件:

this.triggerEvent('timeUpdata',{
   currentTime
})

调用这个子组件的父组件的页面上对应的标签

父组件通过触发自定义事件接受到这个数据currentTime的同时想传递给另一个子组件,
通过定义事件处理函数,通过给另一个需要接收数据的子组件标签上起名一个class

  timeUpdata(event){
    this.selectComponent('.lyric').update(event.detail.currentTime)
  },

这样的话,另一个子组件就可以通过自身的update事件,成功接收到currentTime这个数据了。
注意:这时候子组件的update事件相当于被父组件给调用了一次。

歌词的滚动是利用了组件,scroll-top属性可以规定这个容器里的内容向上滚动的距离,scroll-y属性表示上下方向的滚动,scroll-with-animatio="true"表示开启滚动过渡动画。

scroll-top的属性值只能是px,而我们设置的歌词单行高度是64rpx,不同手机这个rpx代表的实际尺寸都不同,所有这里需要进行一个换算

lifetimes: {
  ready() {
    wx.getSystemInfo({
      success: function(res) {
        lyricHeight = res.screenWidth / 750 * 64
      },
    })
  }
},

小程序宽度是750rpx,把屏幕的宽度除以750,得到的就是1rpx

17.给小程序设置全局属性和方法

app.js中的

    this.globalData = {
      playingMusicId:-1
    }
  },
  setPlayMusicId(musicId){
    this.globalData.playingMusicId = musicId
  },
  getPlayMusicId(){
    return this.globalData.playingMusicId
  }

在页面获取全局的属性或者方法

const app =getApp()
//在播放的音乐的方法中设置属性
app.setPlayMusicId(musicid)

在对应的组件的页面生命周期中,当页面展示的时候,去触发方法获取到全局变量

pageLifetimes: {
  show() {
    this.setData({
      playingId: parseInt(app.getPlayMusicId())
    })
  }
},

小程序下方自带的mini控制面板的暂停和播放对应的也就是背景音乐监听事件中的onPause和onPlay
backgroundAudioManager.onPlaybackgroundAudioManager.onPause

你可能感兴趣的:(云开发练手项目:音乐播放器(上))