新手入门微信小程序简单项目实战一 —— 音乐播放器 详细教学

4,501 阅读6分钟

声明该小程序所有文件内容均来自于网易云音乐

音乐治疗室 MusicCureRoom

写文章不易 动动小手点个赞吧!谢谢。

源码地址可取音乐治疗室

测试小程序,可扫码,查看效果。

新版体验,可申请测试

K)UGTQBZQNX_TB.png 实现了进度条拉伸,自动播放下一首歌曲

最近,微信小程序课程开发中老师在学习通布置了一个这样的任务。写一个音乐播放器。

image.png 大抵功能是实现音乐的播放,暂停,切换歌曲,以及音乐列表和播放进度条等。

于是我开启了小程序开发的路程。

以下先列举一下小程序入门需要的文档。

  • 开发文档

     该文档包含微信小程序开发中所有组件api的介绍以及使用。 
     
    
  • 微信公众平台

    小程序开发上传都需要进行注册,在这里我们可以注册自己需要写的小程序。注册
    完我们就可以登录进入我们的小程序后台,对我们的小程序进行管理。
    
  • 微信开发者工具下载地址与更新日志

      在这里下载好我们适配的版本工具,我们的小程序就是在这里被创建的。
      
    

开启微信小程序项目之路

浅看一下最后的效果吧!

MusicCureRoom.gif

前端人做项目必写数据

这里可以看我上一篇文章,前端爬数据当接口 你还在一个一个的写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属性。

我们用浏览器打开网易云音乐,随便进入一个歌单,可以看到这样的效果。

image.png 因为这种页面里,没有图片,所以当我们拿爬虫去爬的时候,拿不到图片,点开歌曲,图片才会显示。

image.png

按照之前的方法我们应该可以拿到name,author,注意我点击的这个id,这是每首网易云歌曲播放的id,我们可以拿到这个id,然后通过一个正则表达式和字符串拼接拿到src

image.png

重要的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

测试 赵雷——画

介绍

image.png

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;
}