第三章 音乐播放器

1. 开发前准备

1.1 项目展示

音乐推荐
播放器
播放列表

1.2 项目分析

1.页面结构
从图看出,小程序分为三个区域。分别将他们命名为tab,content,player。

tab区域用来显示音乐推荐,播放器,播放列表这三个标签页的标题。如果位于当前页,该前标题文字和标题下方的线条就会变红,其他显示为白色。

content区域通过左右滑动可以实现标签页的切换。

  • 音乐推荐:向用户展示一些热门的音乐。
  • 播放器:显示当前播放的时间和进度,用户可跳转进度。
  • 播放列表:显示当前正在播放的曲目列表,用户可以进行曲目切换。

player用来显示当前正在播放的音乐信息,并提供三个按钮,功能依次为“打开播放列表”,“播放/暂停”,“下一曲”。
2. 目录结构
本程序实际上只有一个页面,即pages/index/index,而wxml被拆分成了四个文件,分别是index.wxml,info.wxml,play.wxml,playlist.wxml,整体结构在index.wxml中编写,content区域由于不同的标签页的结构不同,拆分到了三个wxml文件。

1.3 项目初始化

利用微信开发者工具新建一个项目,然后再pages/index/index.json文件中编入页面配置代码。

{
  "navigationBarBackgroundColor": "#fff",
  "navigationBarTitleText": "音乐",
  "navigationBarTextStyle":"black"
}

指定导航栏背景颜色为白色,背景颜色为黑色,标题为"音乐"。

2. 标签页切换

2.1 分析

本功能为“音乐推荐”,“播放器”,“播放列表”三个标签页的切换效果。
设计思路:跟轮播图很像,content是可见区域,上面三个标签类似于焦点,用户向左划,play进入content可见区域,info进入不可见区域。

2.2 前导知识

1.swiper组件
官方文档的swiper组件介绍
通过官方文档我们知道通过改变current的值,可以切换当前显示哪一项,其值是一个从0开始的索引,对应的顺序。


            
                0
            
            
                1
            
            
                2
            

页面显示1。

除了索引值,我们也可以通过item-id来显示每一个


            
                0
            
            
                1
            
            
                2
            

页面显示1。
2.include代码引用
在wxml中可以使用标签引用其他文件中的代码,相当于将引用的代码复制到标签的位置。

2.3 编写页面结构和样式

首先在pages/index/index.wxml中编写页面结构代码。



  
    音乐推荐
  
  
    播放器
  
  
    播放列表
  



  
    
      
    
    
      
    
    
      
    
  



  

上述代码中,外层结构由三个view标签(相当于div标签)组成,对应了页面的三个基本区域。然后在内容区域使用了swiper组件,用于标签页的切换,还用include标签引入了info,play,playlist三个wxml文件。
接下来在index.wxss中书写样式代码。

page{
  display:flex;
  flex-direction:column;
  background: #17181a;
  color: #ccc;
  height: 100%;
}
.tab{
  display: flex;
}
.tab-item{
  flex: 1;
  font-size: 10pt;
  text-align: center;
  line-height: 72rpx;
  border-bottom: 6rpx solid #eee;
}
.content{
  flex: 1;
}
.content> swiper {
  height: 100%;
}
.player{
  background: #222;
  border-top: 1px solid #252525;
  height: 112rpx;
}

上述代码中,
page使用了flex布局,设置子元素沿垂直方向从上到下排列;将page高度设置为100%,并将content区域设置flex:1,实现页面占满整个屏幕,tab和player分别固定在上方和下方,content的高度自动拉伸为page减去tab和player的高度,适应不同的手机屏幕。
tab区域设为flex布局,将子元素设为flex:1,使这三个子元素沿水平方向从左到右排列,并平局分布每一项的宽度。
设置content区域的swiper组件的高度为100%,从而占领整个content区域。
然后在info.wxml,play.wxml,playlist.wxml中编写简单的代码。
(1)info.wxml

info

(2)play.wxml

play

(3)playlist.wxml

playlist

2.4 实现标签页切换

在本小程序中有两种切换标签页的方法,一种是直接滑动content区域,另一种是单击tab区域中的某一个tab-item切换到对应的标签页。优先实现第二种。
修改index.wxml中tab区域,为三个tab-item绑定事件,并设置data-item属性。
代码如下

  
    音乐推荐
  
  
    播放器
  
  
    播放列表
  

上述代码中,data-item的值表示swiper组件中对应的swiper-item的索引。
接下来为content区域中的swiper组件的current属性绑定变量item。


  

然后在tab-item的changeItem事件中,将item的值设为data-item的值,即可实现切换到对应的标签页。修改index.js文件,将item和changeItem添加到代码中。

Page({
  data: {
    item:0
  },
  changeItem:function(e){
    this.setData({
      item:e.target.dataset.item
    })
  },
······

在切换标签页后,还需要修改当前标签页对应的tab-item的样式。下面通过判断变量tab的值,来为当前活跃的data-item增加一个active样式,代码如下。

  
    音乐推荐
  
  
    播放器
  
  
    播放列表
  

在index.wxss中编写active样式

.tab-item.active{
  color: #c25b5b;
  border-bottom-color:#c25b5b;
}

为了更改tab的值,下面为swiper组件绑定事件,代码如下。


  

上述代码中,changeTab事件处理函数会在swiper组件发生标签页切换时调用,接下来修改index.js,在页面数据中加入tab变量。

Page({
  data: {
    item:0,
    tab:0
  },

然后编写changeTab事件处理函数,将tab值与当前标签页一致。

changeTab:function(e){
    this.setData({
      tab:e.detail.current
    })
  },

切换标签页功能完成。

3 音乐推荐

3.1 分析

音乐推荐(content-info)是content区域中的一个标签页,其内容由三部分组成,分别是轮播图(content-info-slide),功能按钮(content-info-portal),热门音乐(content-info-list)。
content区域是swiper组件,该标签页位于索引为0的标签中。
由于content区域的可显示高度是有限的·,所以采用scroll-view组件,用于实现内容的上下滚动。

3.2 前导知识

1.scroll-view组件
官方文档的scroll-view组件介绍
2.image组件
官方文档的image组件介绍

3.3 内容区域滚动

在info.wxml中编写音乐推荐标签页中的代码。代码如下。


接下来在index.wxss中将scroll-view的高度设为100%,代码如下。

.content-info{
  height: 100%;
}

如果希望隐藏滚动条,还可加入以下代码。

/*隐藏滚动条*/
::-webkit-scrollbar{
  width: 0;
  height: 0;
  color: transparent;
}

3.4 轮播图

轮播图位于音乐推荐标签页的最上方,切换效果通过swiper组件来实现,图片通过image组件(可本地图片也可在网上找,我引入的是网易云音乐的轮播图,以后网易换了,就引别的)引入。在info.wxml的scroll-view组件中编写如下代码。


上述代码中,为swiper组件设置了指示点的颜色为白色半透明,当前选中的指示点颜色为白色不透明。
接下来在index.wxss中编写样式,代码如下。

.content-info-slide{
  height: 302rpx;
  margin-bottom: 20px;
}
.content-info-slide image{
  width: 100%;
  height: 100%;
}

实现效果如下。


轮播图

3.5 功能按钮

功能按钮位于轮播图的下方,在info.wxml中编写页面结构,代码如下。(图片可以在阿里矢量库中下载)

    

接下来在index.wxss中编写样式代码吗,代码如下。

.content-info-portal{
  display: flex;
  margin-bottom: 15px;
}
.content-info-portal>view{
  flex: 1;
  font-size: 11pt;
  text-align: center;
}
.content-info-portal image{
  width: 120rpx;
  height: 120rpx;
  display: block;
  margin: 20rpx auto;
}

上述代码中,设置了外层容器为flex布局,然后设置内层view为flex:1,从而使3个view标签平均分配整个容器的宽度。
编写玩代码,实现效果如下。


功能按钮

3.6 热门音乐

热门音乐位于功能按钮的下方,实现思路与功能按钮类似,在info.wxml中功能按钮代码的下方编写热门音乐代码。代码如下。


接下在index.wxss中编写样式代码,代码如下。

.content-info-list{
  font-size: 11pt;
  margin-bottom: 20rpx;
}
.content-info-list>.list-title{
  margin: 20rpx 35rpx;
}
.content-info-list>.list-inner{
  display: flex;
  flex-wrap: wrap;
  margin: 0 20rpx;
}
.content-info-list>.list-inner>.list-item{
  flex: 1;
}
.content-info-list>.list-inner>.list-item>image{
  display: block;
  width: 200rpx;
  height: 200rpx;
  margin: 0 auto;
  border-radius: 10rpx;
  border: 1rpx solid #555;
}
.content-info-list>.list-inner>.list-item>view{
  width: 200rpx;
  margin: 10rpx auto;
  font-size: 10pt;
}

实现效果如下。


热门音乐

4 播放器

4.1 分析

播放器(content-play)是content区域中的一个标签页,其内容由三部分组成,分别是音乐信息(content-play-info),专辑封面(content-play-cover),播放进度(content-play-progress)。该标签页位于索引为1的标签中。
明确页面结构后,对播放器的功能进行分析。具体如下。

  • 音乐信息:显示当前播放曲目的标题和艺术家。
  • 专辑封面:当音乐播放时,专辑封面会顺时针旋转。
  • 播放进度:显示当前曲目的播放时长和总时长,并提供一个进度条,当音乐播放时进度条就会增长,用户也可以手动改变进度条的进度来跳转播放进度。

4.2 前导知识

1.音频API
官方文档的介绍
2.slide组件(用于进度条)
官方文档的介绍

4.3 定义基础数据

在index.js的data对象中定义基础数据playlist,主要包括音乐的信息和音乐播放路径,代码如下所示。

playlist:[{
      id:1,
      title:'玄武之死',
      singer:'黎允文',
      src:'http://isure.stream.qqmusic.qq.com/C400002MIiSk3m0nse.m4a?guid=6844422626&vkey=DD7562F237BE001039E20BA8BB4081EF045CDE88C6C141EFF338772AF1CC4FD08004C64E00CFB590AEDCF5AA6F19128A7D51C804A07615AA&uin=0&fromtag=66',
      coverImgUrl:'https://y.gtimg.cn/music/photo_new/T002R300x300M000002hjNNE2TooAe.jpg?max_age=2592000'
    },{
      id:2,
      title:'Without U',
      singer:'Sergio',
      src:'http://isure.stream.qqmusic.qq.com/C400004gVyzF0D1PPf.m4a?guid=6844422626&vkey=5CE954664D201CF3DCED19F6FD3A8DB6F0D58E83877411A4DE3F466D482A8D8D721CE5477295E986FBB8B6A67344BBF92262D47CFB44F5A3&uin=0&fromtag=66',
      coverImgUrl:'https://y.gtimg.cn/music/photo_new/T002R300x300M000003meScq4WJVQ6.jpg?max_age=2592000'
    },{
      id:3,
      title:'美丽的神话',
      singer:'金喜善 (김희선) _ 成龙',
      src:'http://isure.stream.qqmusic.qq.com/C400000k9jj51TOfNy.m4a?guid=6844422626&vkey=CCE79A2C68488D98C0D842313BABE5358E30B9FA77715F2E2FA99DFC4046701D8569964639A4A6F2D6E0DAFAEEF869371D02B063A213D1E4&uin=0&fromtag=66',
      coverImgUrl:'https://y.gtimg.cn/music/photo_new/T002R300x300M0000023YRbq0UUpdn.jpg?max_age=2592000'
    },{
      id:4,
      title:'Daisuke',
      singer:'横田商会',
      src:'http://27.221.15.143/amobile.music.tc.qq.com/C400004NHvoq4bezhA.m4a?guid=6844422626&vkey=2835623C04A146286277B62B4AD94F83E43D158940D9D11C864668209EE9A6173416A6821A0C6BBC10E602900B4470595B393A2F80906EF4&uin=0&fromtag=66',
      coverImgUrl:'https://y.gtimg.cn/music/photo_new/T002R300x300M000000bdyFf1DKofx.jpg?max_age=2592000'
    },{
      id:5,
      title:'Pokemon Ü',
      singer:'It`s different',
      src:'http://ws.stream.qqmusic.qq.com/C400003ly9LE18HHpK.m4a?guid=6844422626&vkey=E92C38E8583B3DE8701B08A7A1301F4BB63E004DFAB64923FDB15F7FBBCDBD10550351279E71FF47829FA58CCB6DE4E0DA7ACDA223C53CFD&uin=0&fromtag=66',
      coverImgUrl:'https://y.gtimg.cn/music/photo_new/T002R300x300M0000045liZB2NGp3I.jpg?max_age=2592000'
    },{
      id:6,
      title:'Miss You',
      singer:'Fox Stevenson',
      src:'http://ws.stream.qqmusic.qq.com/C400003jErC91gSJKz.m4a?guid=6844422626&vkey=52E72CB9C7B2496FC36DEB37296E39200A417F51F68FD16C0F85E233ED0D5757F0D75769588BA361D72BBF4A112E69F842CDE7F7983A063F&uin=0&fromtag=66',
      coverImgUrl:'https://y.gtimg.cn/music/photo_new/T001R300x300M000001Rthwx2mTz1n.jpg?max_age=2592000'
    },{
      id:7,
      title:'Alone',
      singer:'Marshmello',
      src:'http://ws.stream.qqmusic.qq.com/C400001T5sEj19MXkW.m4a?guid=6844422626&vkey=8656483DCD374965B5189B65617BB87B5726883F5144CF3C735AE22346859414C73FBD17DBF1361C1A09074B8722AE6CA95B53D652C9BFED&uin=0&fromtag=66',
      coverImgUrl:'https://y.gtimg.cn/music/photo_new/T002R300x300M000000SBmte03ICax.jpg?max_age=2592000'
    }
  ],

上述代码中,
id是每条记录的唯一标识,
title是歌名,
singer是歌手,
src是网络中的音频文件链接地址,这里我找的是qq音乐的资源,方法就是点击播放后右键检查元素,看网络,然后把文件类型设置为媒体,最低下那个就是音乐然后复制值填入src就行,但是它每天都会换,一整就无效,emmm,当然你也可以自己搭个服务器,
coverImgUrl是专辑封面图片的链接地址,当然也可以用本地的。
播放列表数据准备好后,还需要再data中定义一些状态属性,来记录音乐的播放状态,播放位置等,代码如下。

state:'paused',
  playIndex:0,
  play:{
    currentTime:'00:00',
    duration:'00:00',
    percent:0,
    title:'',
    singer:'',
    coverImgUrl:'/images/test.jpg'
  }

上述代码中,
state表示音乐的播放状态,paused表示暂停,running表示正在播放,
playIndex表示当前播放的歌曲在播放列表中索引,
play对象记录了当前播放曲目的信息,
currentTime表示播放时长,
duration表示总时长,
percent表示播放进度,
title,singer,coverImgUrl,分别表示,歌名,歌手,专辑封面。

4.4 实现音乐播放功能

音乐的播放功能需要通过调用微信小程序的wx.createInnerAudioContext()接口获得一个audioCtx对象,然后利用audioCtx对象的属性和方法来实现音乐控制。
在index.js中编写代码,实现在页面打开时,自动选择播放列表的第一个曲目,代码如下。

audioCtx:null,
  onReady:function(){
    this.audioCtx=wx.createInnerAudioContext();
    //默认选择第一曲
    this.setMusic(0);
  },
  setMusic:function(index){
    var music=this.data.playlist[index];
    this.audioCtx.src=music.src;
    this.setData({
      playIndex:index,
      'play.title':music.title,
      'play.singer':music.singer,
      'play.coverImgUrl':music.coverImgUrl,
      'play.currentTime':'00:00',
      'play.duration':'00:00',
      'play.percent':0
    });
  },

上述代码中,setMusic()函数用于切换当前播放的曲目,其参数index表示播放列表数组中的某一个成员的索引值,根据索引值到播放列表中取出音乐信息之后,将src赋值给音频接口,然后将曲目信息渲染到页面。
接下来在index.wxml中编写底部播放器的代码,代码如下。



  
  
    
      {{play.title}}
    
    
      {{play.singer}}
    
  
  
    
    
    
    
    
    
  

上述代码中,图片是从阿里矢量库里下载的。
然后在index.wxss中编写样式,代码如下。

.player{
  display: flex;
  align-items: center;
  background: #222;
  border-top: 1px solid #252525;
  height: 112rpx;
}
.play-cover{
  width: 80rpx;
  height: 80rpx;
  margin-left: 15rpx;
  border-radius: 8rpx;
  border: 1px solid #333;
}
.play-info{
  flex: 1;
  font-size: 10pt;
  line-height: 38rpx;
  margin-left: 20rpx;
  padding-bottom: 8rpx;
}
.play-info-singer{
  color: #888;
}
.play-controls image{
  width: 80rpx;
  height: 80rpx;
  margin-right: 15rpx;
}

上述代码中,父元素设置flex布局,内部音乐封面和播放按钮设置为固定宽高,放在页面两端,中间部分是音乐信息的显示区域。
实现效果。


播放器

为了实现单击播放按钮播放音乐,再次点击暂停音乐的效果,修改播放按钮的代码,利用条件渲染判断state的值,来显示不同的按钮,代码如下。


    
    

在index.js中编写play()和pause(),代码如下。

play:function(){
    this.audioCtx.play();
    this.setData({state:'running'})
  },
  pause:function(){
    this.audioCtx.pause();
    this.setData({state:'paused'})
  },

上述代码中,分别调用了audioCtx对象的play()和pause()方法,实现音乐的播放与暂停,同时更改当前音乐的播放状态state。
接下来还需要实现单击下一曲按钮进行换曲,在index.wxml中为按钮绑定next()事件处理函数,代码如下。

    
    

在index.js中编写事件处理函数next(),代码如下。

next:function(){
    var index=this.data.playIndex>=this.data.playlist.length-1 ? 0:this.data.playIndex + 1;
    var that=this;
    this.setMusic(index);
    if(this.data.state==="running"){
      this.play();
    }
  },

上述代码中,index用于增加当前播放的播放列表的索引,通过判断避免超过最大索引,使其在播放列表循环,还判断了当前的播放状态,如果当前状态为暂停,不会立即播放。
不知道是不是我看的书有问题(微信读书看的,白送30天,美汁儿汁儿),这代码有bug,就是在后面写进度条那段,点击下一曲之后,进度条全变为默认的00:00,所以我又在next()中加了一段代码来使下一曲之后重新设置当前曲目的总时长。代码如下。

this.audioCtx.onCanplay(function(){
      that.setData({
        'play.duration':that.formatTime(that.audioCtx.duration),
      })
    })

上述代码中,使用了audioCtx的onCanplay方法,在音乐进入可播放状态时,设置总时长为当前歌曲的总时长,formatTime是调整时间格式的函数,与next()同级,之后在页面初次渲染时也会用到。代码如下。

formatTime:function(time){
    var minute=Math.floor(time/60)%60;
    var second=Math.floor(time)%60;
    return (minute <10?'0'+minute:minute)+':'+(second<10?'0'+second:second)
  },

接下来还需要实现单击播放列表按钮跳转到播放列表标签页,在index.wxml中为按钮绑定changePage()事件处理函数,代码如下。

    
    

在index.js中编写事件处理函数changePage(),代码如下。

changePage:function(e){
    this.setData({
      tab:e.currentTarget.dataset.page,
      item:e.currentTarget.dataset.page
    })
  },

上述代码中,点击按钮后将tab和item设置为2,实现跳转。

4.5 编写播放器页面

播放器页面主要用于显示当前播放曲目的信息,以及显示播放进度和时间。页面中显示的信息都可以在data数据中获取。在play.wxml中编写页面结构,代码如下。


        
        
            {{play.title}}
            ——{{play.singer}}——
        
        
        
        
        
        
        
    

在index.wxss中编写样式,代码如下。

.content-play{
  display: flex;
  justify-content: space-around;
  flex-direction: column;
  height: 100%;
  text-align: center;
}
.content-play-info>view{
  color: #888;
  font-size: 11pt;
}

实现效果如下。


音乐信息

接下来在音乐信息的下方展示音乐专辑封面,在音乐列表中获取到当前音乐专辑封面的src路径,绑定到页面image组件中,代码如下。

        
        
            
        

上述代码中,animation-play-state用于控制专辑封面旋转动画的播放和暂停。
完成结构代码后,在index.wxss中编写样式,代码如下。

.content-play-cover image{
  animation: rotateImage 10s linear infinite;
  width: 400rpx;
  height: 400rpx;
  border-radius: 50%;
  border: 1px solid #333;
}
@keyframes rotateImage{
  from{
    transform: rotate(0deg);
  }
  to{
    transform: rotate(360deg);
  }
}

实现效果如下。


专辑封面

4.6 控制播放进度

在播放器页面的底部,用于显示播放进度,通过调节滑块的位置可以控制播放进度。在play.wxml中编写播放进度结构代码,代码如下。


        
            
               {{play.currentTime}} 
            
            
                
            
            
                {{play.duration}}
            
        

上述代码中,用到了slide组件,其值为播放进度play.percent。
接下来在index.wxss中编写样式,代码如下。

.content-play-progress{
  display: flex;
  align-items: center;
  margin: 0 35rpx;
  font-size: 9pt;
  text-align: center;
}
.content-play-progress>view{
  flex: 1;
}
.content-play-progress>text{
  color: white;
  margin-top: -35rpx;
}

效果如下。


进度条

然后在index.js中控制进度条的进度和时间显示(修改onReady函数),代码如下。

onReady:function(){
    this.audioCtx=wx.createInnerAudioContext();
    var that=this;
    //播放失败检测
    this.audioCtx.onError(function(){
      console.log('播放失败'+that.audioCtx.src);
    })
    //播放完成自动换下一曲
    this.audioCtx.onEnded(function(){
      that.next();
    })
    //自动更新播放进度
    this.audioCtx.onPlay(function(){})
    this.audioCtx.onTimeUpdate(function(){
      that.setData({
        'play.duration':that.formatTime(that.audioCtx.duration),
        'play.currentTime':that.formatTime(that.audioCtx.currentTime),
        'play.percent':that.audioCtx.currentTime/that.audioCtx.duration*100
      })
    })
    //默认选择第一曲
    this.setMusic(0);
  },

上述代码中,通过调用audioCtx的onTimeUpdate()方法,获取音乐状态信息,并通过formatTime()函数处理时间格式,最后渲染到页面实现定时更新效果。
在slide组件上绑定bindchange事件,可以实现滑动进度条调节音乐播放进度。


在index.js中编写slideChange函数获取当前用户选择的进度,将时间通过audioCtx对象的seek()方法进行设置,代码如下。

  slideChange:function(e){
    var that=this;
    var second=e.detail.value*this.audioCtx.duration/100;
    this.audioCtx.seek(second);
  },

上述代码中,我运行之后发现了一个bug,在用户调节进度条之后,进度条左边的播放时间并不会跟着一起变,所以我在slideChange中添加了一些代码达到此功能,代码如下。

this.audioCtx.onSeeked(function(){
      that.setData({
        'play.duration':that.formatTime(that.audioCtx.duration),
        'play.currentTime':that.formatTime(that.audioCtx.currentTime),
        'play.percent':that.audioCtx.currentTime/that.audioCtx.duration*100
      })
    })

上述代码使用了audioCtx对象的onSeeked()方法,当发生跳转时间时,会重新渲染页面。

5 播放列表

5.1 分析

播放列表(content-play)是content区域中的最后一个标签页,其内容是一个列表,通过列表渲染(wx:for循环)自动生成。该标签页位于索引为2的标签中。

5.2 编写页面结构和样式

播放列表的数据在前面就已经定义,下面在playlist.wxml中编写页面,代码如下。


        
            
            
                
                    {{item.title}}
                
                
                    {{item.singer}}
                
            
            
                
                    正在播放
                
            
        
    

上述代码中,wx:if判断当前播放的索引playIndex是否与当前播放列表数据的索引In,如果index相同则显示正在播放。
在index.wxss中编写样式,代码如下。

.playlist-item{
  display: flex;
  align-items: center;
  border-bottom: 1rpx solid #333;
  height: 112rpx;
}
.playlist-cover{
  width: 80rpx;
  height: 80rpx;
  margin-left: 15rpx;
  border-radius: 8rpx;
  border: 1px solid #333;
}
.playlist-info{
  flex: 1;
  font-size: 10pt;
  line-height: 28rpx;
  margin-left: 20rpx;
  padding-bottom: 8rpx;
}
.playlist-info-singer{
  margin-top: 15rpx;
  color: #888;
}
.playlist-controls{
  font-size: 10pt;
  margin-top: -50rpx;
  margin-right: 20rpx;
  color: #c25b5b;
}

实现效果如下。


播放列表

5.3 实现换曲功能

当用户单击播放列表中的某一项之后,会触发执行对应的曲目。在index.js中编写代码。

change:function(e){
    this.setMusic(e.currentTarget.dataset.index);
    this.play();
  }

你可能感兴趣的:(第三章 音乐播放器)