[
  {
    "path": "README.md",
    "content": "### 扫码预览\n\n<div align=center><img src=\"http://img.22family.com/mySKey/small.jpg\" /></div>\n\n### 一、需求分析\n\n* 基本功能\n\n上一曲、下一曲、播放、暂停、列表播放\n\n* 切换到别的页面播放继续\n\n* 歌词同步\n\n1、歌词随音乐进度更新\n2、拖动歌词后出现可调节位置的控件，此时歌词滚动暂停\n3、点击控件播放，关闭控件，改变进度\n4、不点击，5s后关闭控件\n\n* 进度条播放\n\n可点击或者拖动进度条来改变歌曲进度\n\n* 播放模式\n\n单曲、列表、随机三种播放模式\n\n* 倍速\n\n可在不同倍速下播放，并且不同倍速下歌词同步\n\n\n### 其中的经验：\n\n* 1、wxs支持es4的语法\n* 2、在app.json中设置\"requiredBackgroundModes\": [\"audio\"]，将开启后台播放\n* 3、wx.getBackgroundAudioManager()的onEnded监听在安卓机上面不生效\n* 4、不支持倍速"
  },
  {
    "path": "app.js",
    "content": "//app.js\nApp({\n  onLaunch: function () {\n    wx.setNavigationBarTitle({\n      title: 'mySkey音乐'\n    })\n    this.initAudio()\n  },\n  onShow(){\n    if(this.audioDom.paused){\n      this.player.status = 2;\n    }\n  },\n  onHide: function () {\n    \n  },\n  player: {\n    list: [],\n    current_music: 0,\n    mode: 0,            // 0 单曲    1 顺序   2 随机\n    status: 0,          // 0 未播放  1 播放   2 暂停中  3 已结束\n    playbackRate: 1,    // 倍速\n    a_resource: '',\n    i_resource: '',\n    global_show: 0,     // 全局显示  0 不显示 1 显示\n    playing: {\n      id: '',\n      name: '',\n      cover: '',\n      url: '',\n      singer: {\n        avatar: '',\n        name: ''\n      },\n      currentTime: 0,\n      duration: 0,\n      timeArr: [],\n      lrcArr: []\n    }\n  },\n  audioDom: wx.getBackgroundAudioManager(),\n  initAudio(){\n    this.audioDom.title = '起风了';\n    this.audioDom.epname = '555';\n    this.audioDom.singer = 'mySkey';\n    this.audioDom.coverImgUrl = 'http://img.22family.com/mySKey/favicon.ico';\n    this.audioDom.paused = true;\n    this.audioDom.stop = true;\n  }\n})"
  },
  {
    "path": "app.json",
    "content": "{\n  \"pages\": [\n    \"pages/music/list\",\n    \"pages/music/detail\"\n  ],\n  \"window\": {\n    \"backgroundTextStyle\": \"light\",\n    \"navigationBarBackgroundColor\": \"#fff\",\n    \"navigationBarTitleText\": \"WeChat\",\n    \"navigationBarTextStyle\": \"black\"\n  },\n  \"requiredBackgroundModes\": [\"audio\"]\n}"
  },
  {
    "path": "app.wxss",
    "content": "/**app.wxss**/\n@import './utils/wxss/reset.wxss';\n@import './utils/wxss/icon.wxss';\n@import './utils/wxss/style.wxss';\n.container {\n  height: 100%;\n  box-sizing: border-box;\n}"
  },
  {
    "path": "components/controls.js",
    "content": "// components/controls.js\nconst app = getApp()\nimport props from '../utils/js/props.js'\nimport common from '../utils/js/common.js'\nComponent({\n  /**\n   * 组件的属性列表\n   */\n  properties: {\n    currentTime: {\n      type: Number,\n      value: 0,\n      observer: function (newVal, oldVal, changedPath) {\n        props.setPlaying.call(this, { currentTime: newVal, duration: app.audioDom.duration })\n        //props.setPlayer.call(this, { status: app.player.status })\n      }\n    }\n  },\n\n  /**\n   * 组件的初始数据\n   */\n  data: {\n    player:{},\n  },\n  attached(){\n    props.setPlayer.call(this, app.player)\n  },\n  /**\n   * 组件的方法列表\n   */\n  methods: {\n    pauseAudio() {\n      app.audioDom.pause()\n      props.setPlayer.call(this, { status: 2 })\n    },\n    palyAudio() {\n      app.audioDom.play()\n      props.setPlayer.call(this, { status: 1 })\n    },\n    playLast() {\n      if (this.data.player.list.length > 0) {\n        this.triggerEvent('playLast')\n      }\n    },\n    playNext() {\n      if (this.data.player.list.length > 0) {\n        this.triggerEvent('playNext')\n      }\n    },\n    changeMode() {\n      let mode = this.data.player.mode + 1\n      if (mode > 2) {\n        mode = 0\n      }\n      props.setPlayer.call(this, { mode })\n    },\n    changeRate() {\n      common.toast('小程序不支持倍速功能！')\n      return\n      let playbackRate = this.data.player.playbackRate + 0.25\n      if (playbackRate > 2) playbackRate = 0.5\n      props.setPlayer(this, { playbackRate })\n      global.audioDom.playbackRate = playbackRate;\n    },\n    progressTouch() {\n      this.setState({ userChange: true })\n    },\n    changeProgress(radio) {\n      let currentTime = Math.floor(radio * this.props.audio.duration)\n      global.audioDom.currentTime = currentTime\n      this.props.setCurrentTime(currentTime)\n      this.setState({ userChange: false })\n    }\n  }\n})\n"
  },
  {
    "path": "components/controls.json",
    "content": "{\n  \"component\": true,\n  \"usingComponents\": {\n    \"myProgress\": \"/components/progress\"\n  }\n}"
  },
  {
    "path": "components/controls.wxml",
    "content": "<!--components/controls.wxml-->\n<wxs module=\"time\" src=\"../utils/wxs/time.wxs\"></wxs>\n\n<view class='controls'>\n  <view class='time df-j-c'>\n    <view>{{time.getAudioTime(player.playing.currentTime)}}</view>\n    <view class='progress'>\n      <myProgress currentTime=\"{{player.playing.currentTime}}\"></myProgress>\n    </view>\n    <view>{{time.getAudioTime(player.playing.duration)}}</view>\n  </view>\n\n  <view class='btns df-j-c'>\n    <view bindtap='changeMode' class='mode_rete df-j-c'>\n      <i wx:if=\"{{player.mode === 0}}\" class='mode iconfont icon-only'></i>\n      <i wx:if=\"{{player.mode === 1}}\" class='mode iconfont icon-next'></i>\n      <i wx:if=\"{{player.mode === 2}}\" class='mode iconfont icon-suiji'></i>\n    </view>\n    <view class='play_btn df-j-c'>\n      <i bindtap='playLast' class='last_next iconfont icon-last-play'></i>\n      <view>\n        <i bindtap='pauseAudio' wx:if=\"{{player.status == 1}}\" class='play_pause iconfont icon-pause'></i>\n        <i bindtap='palyAudio' wx:else class='play_pause iconfont icon-play'></i>\n      </view>\n      <i bindtap='playNext' class='last_next iconfont icon-next-play'></i>\n    </view>\n    <view bindtap='changeRate' class='mode_rete df-j-c'>\n      <view wx:if=\"{{player.playbackRate === 1}}\">正常</view>\n      <view wx:else>×{{player.playbackRate}}</view>\n    </view>\n  </view>\n</view>\n"
  },
  {
    "path": "components/controls.wxss",
    "content": "/* components/controls.wxss */\n@import '/utils/wxss/style.wxss';\n@import '/utils/wxss/icon.wxss';\n.controls{\n  width: 100%;\n  position: absolute;\n  bottom: 2rem;\n  left: 0;\n}\n.controls .time{\n  color: #fff;\n  font-size: 0.6rem;\n  align-items: center;\n}\n.controls .time .progress{\n  width: 10rem;\n  margin: 0 16px;\n}\n\n.controls .btns{\n  align-items: center;\n  color: #dd5866;\n}\n.controls .btns .play_btn{\n  align-items: center;\n  margin: 0 2rem;\n}\n.controls .btns .last_next{\n  font-size: 1.2rem;\n}\n.controls .btns .play_pause{\n  font-size: 2.4rem;\n  margin: 0 1rem;\n}\n\n.controls .btns .mode_rete{\n  align-items: center;\n  font-size: 0.7rem;\n  color: #fff;\n  min-width: 40px;\n}\n.controls .btns .mode_rete .mode{\n  font-size: 1rem;\n}"
  },
  {
    "path": "components/loadmore.js",
    "content": "// components/loadmore.js\nComponent({\n  /**\n   * 组件的属性列表\n   */\n  properties: {\n    loading: {\n      type: Number,\n      value: 0,\n      observer: function (newVal, oldVal, changedPath) {\n        this.setData({ _loading: newVal });\n      }\n    }\n  },\n\n  /**\n   * 组件的初始数据\n   */\n  data: {\n    _loading: 0\n  },\n\n  /**\n   * 组件的方法列表\n   */\n  methods: {\n\n  }\n})"
  },
  {
    "path": "components/loadmore.json",
    "content": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "components/loadmore.wxml",
    "content": "<!--components/loadmore.wxml-->\n<view class='component-container wrap'>\n  <view class='loading-dot' wx:if='{{_loading==1}}'>\n    <view class='dot dot-1'></view>\n    <view class='dot dot-2'></view>\n    <view class='dot dot-3'></view>\n  </view>\n  <view class='all' wx:elif='{{_loading==2}}'>已加载全部</view>\n  <view class='placeholder' wx:else>　</view>\n</view>\n"
  },
  {
    "path": "components/loadmore.wxss",
    "content": "/* components/loadmore.wxss */\n.wrap{padding:20rpx 0 50rpx 0;}\n.loading-dot{\n  width:100%; height:40rpx; overflow:hidden; display:flex;\n  align-items:center; justify-content:center;\n}\n.loading-dot .dot{\n  display:inline-block; width:16rpx; height:16rpx; border-radius:50%; overflow: hidden; \n  background:#f2f2f2; margin:0 10rpx;\n  -webkit-animation: bouncedelay 1s infinite ease-in-out;\n  animation: bouncedelay 1s infinite ease-in-out;\n  -webkit-animation-fill-mode: both;\n  animation-fill-mode: both\n}\n\n.loading-dot .dot-1 {-webkit-animation-delay: -.48s; animation-delay: -.48s}\n.loading-dot .dot-2 {-webkit-animation-delay: -.24s; animation-delay: -.24s}\n@keyframes bouncedelay {\n  0%,100%,80% {background:lightgray;}\n  40% {background:gray;}\n}\n/* @keyframes bouncedelay {\n  0%,100%,80% {transform: scale(0); -webkit-transform: scale(0)}\n  40% {transform: scale(1);-webkit-transform: scale(1)}\n} */\n\n.all{font-size:24rpx;color:#e8e8e8;text-align:center;}\n.placeholder{font-size:24rpx;}"
  },
  {
    "path": "components/lrc.js",
    "content": "// components/lrc.js\nconst app = getApp()\nimport props from '../utils/js/props.js'\nimport common from '../utils/js/common.js'\nComponent({\n  /**\n   * 组件的属性列表\n   */\n  properties: {\n    currentTime: {\n      type: Number,\n      value: 0,\n      observer: function (newVal, oldVal, changedPath) {\n        if (app.player.playing.lrcArr.length > 0){\n          props.setPlaying.call(this, { currentTime: newVal })\n          this.getCurrentLrc()\n        }\n      }\n    }\n  },\n\n  /**\n   * 组件的初始数据\n   */\n  data: {\n    player:{},\n    currentLrc: 0,\n    currentWidth: 0,\n    listTop: 0,\n    useTime: 1,\n    userChange: false,\n    changeTo: 0\n  },\n  attached() {\n    //console.log(app.player)\n    this.setData({ player: app.player })\n  },\n\n  /**\n   * 组件的方法列表\n   */\n  methods: {\n    getCurrentLrc() {\n      let player = app.player, audio = app.player.playing;\n      let { currentTime } = audio\n      let currentLrc = 0;\n      audio.timeArr.forEach((v, k) => {\n        if (currentTime > this.getSecond(v)) {\n          currentLrc = k\n        }\n      })\n\n      // 更新当前歌词\n      if (currentLrc !== this.data.currentLrc) {\n        this.setData({ currentLrc, currentWidth: 0 }, () => {\n          this.getCurrentWidth((currentWidth)=>{\n            if (currentLrc < audio.timeArr.length - 1) {\n              let useTime = this.getSecond(audio.timeArr[currentLrc + 1]) - this.getSecond(audio.timeArr[currentLrc])\n              // 根据当前的倍速来决定时间\n              useTime = useTime / player.playbackRate\n              if (useTime > 10) {\n                this.setData({ useTime: 10 })\n                return\n              }\n              this.setData({ useTime, currentWidth })\n            }\n          })\n        })\n        if (!this.data.userChange) {\n          this.scrollLrc(currentLrc)\n        }\n      } else {\n        this.getCurrentWidth((currentWidth)=>{\n          this.setData({ currentWidth })\n        })\n      }\n    },\n    getCurrentWidth(cb){\n      let query = wx.createSelectorQuery().in(this)\n      query.select('.lrcItemShow').boundingClientRect(function (res) {\n        //console.log(res.width)\n        if(res){\n          cb && cb(res.width)\n        }\n      }).exec()\n    },\n    getSecond(t) {\n      let minute = Number(t.slice(0, 2))\n      let second = Number(t.slice(3, 5))\n      let minS = Number(t.slice(7))\n      return minS > 100 ? (minute * 60 + second + minS / 1000) : (minute * 60 + second + minS / 100)\n    },\n    // 歌词滚动\n    scrollLrc(currentLrc) {\n      if (currentLrc) {\n        let listTop = currentLrc * 32\n        this.setData({ listTop })\n      }\n    },\n    // 滑动歌词调整进度\n    handleMove() {\n      if(this.data.userChange){\n        let query = wx.createSelectorQuery().in(this)\n        query.select('.lrcList').boundingClientRect((res)=>{\n          //console.log(res.top / 32)\n          let currentLrc = (-Math.round(res.top / 32)) + 3\n          let time = app.player.playing.timeArr[currentLrc]\n          let currentTime = Math.ceil(common.getSecond(time))\n          this.setData({ changeTo: currentTime })\n        }).exec()\n      }\n    },\n    changeUserStatus(){\n      clearTimeout(this.timer)\n      this.setData({ userChange: true })\n      this.timer = setTimeout(() => {\n        this.setData({ userChange: false })\n      }, 5000)\n    },\n    changeToLrc() {\n      this.setData({ userChange: false }, () => {\n        wx.seekBackgroundAudio({\n          position: this.data.changeTo,\n        })\n      })\n    }\n  }\n})\n"
  },
  {
    "path": "components/lrc.json",
    "content": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "components/lrc.wxml",
    "content": "<!--components/lrc.wxml-->\n<wxs module=\"time\" src=\"../utils/wxs/time.wxs\"></wxs>\n\n<view class='lrc_container'>\n  <scroll-view \n    scroll-y\n    style= \"height:calc(100vh - 11rem)\"\n    bindtouchstart='changeUserStatus'\n    scroll-top=\"{{listTop}}\"\n    bindscroll=\"handleMove\"\n  >\n    <view class='lrcList df-col'>\n      <block wx:for=\"{{player.playing.lrcArr}}\" wx:for-item=\"v\" wx:for-index=\"k\" wx:key=\"k\">\n        <view class='lrcItem'>\n          <view style=\"visibility: {{currentLrc === k ? 'hidden' : 'visible'}}; font-size: {{currentLrc === k ? '1rem' : '0.7rem'}}\">{{v}}</view>\n          <view wx:if=\"{{currentLrc === k}}\" class='lrcItemShow'>{{v}}</view>\n          <view style=\"width: {{currentWidth}}px; transition: all ease {{useTime}}s;\" wx:if=\"{{currentLrc === k}}\" class='lrcItemShowIng'>{{v}}</view>\n        </view>\n      </block>\n    </view>\n  </scroll-view>\n\n  <view wx:if=\"{{userChange}}\" class='swiper df-j-b'>\n    <view>{{time.getAudioTime(changeTo)}}</view>\n    <view class='line'></view>\n    <view bindtap='changeToLrc' class='play'>\n      <i class='iconfont icon-play'></i>\n    </view>\n  </view>\n</view>\n"
  },
  {
    "path": "components/lrc.wxss",
    "content": "/* components/lrc.wxss */\n@import '/utils/wxss/style.wxss';\n@import '/utils/wxss/icon.wxss';\n.lrc_container{\n  position: absolute;\n  top: 80px;\n  left:0;\n}\n.lrcList{\n  width: 100vw;\n  text-align: center;\n  align-items: center;\n  padding-top: 160px;  /* 从第6句开始特殊滑动 */\n  padding-bottom: 3rem;\n}\n.lrcList .lrcItem{\n  color: #fff;\n  height: 32px;\n  line-height: 32px;\n  position: relative;\n  white-space: nowrap;\n}\n.lrcList .lrcItemShow{\n  position: absolute;\n  color: #fff;\n  font-size: 1rem;\n  top:0;\n  left:0;\n  z-index: 98;\n  white-space: nowrap;\n}\n.lrcList .lrcItemShowIng{\n  position: absolute;\n  color: #dd5866;\n  font-size: 1rem;\n  top:0;\n  left:0;\n  z-index: 99;\n  white-space: nowrap;\n  overflow: hidden;\n  width: 0;\n}\n\n.swiper{\n  width: 100%;\n  height: 20px;\n  line-height: 20px;\n  padding: 0 1rem;\n  box-sizing: border-box;\n  position: absolute;\n  top: 166px;   /* 从第六上面出现刻度线 */\n  left: 0;\n  align-items: center;\n  font-size: 28rpx;\n  color: #db3e4b\n}\n.swiper .line{\n  width: 70%;\n  height: 1px;\n  background: radial-gradient(at 50% 0, transparent, transparent, rgba(252, 231, 231, 0.7),rgba(252, 231, 231, 0.7));\n}\n.swiper .play .icon-play{\n  font-size: 40rpx;\n}"
  },
  {
    "path": "components/player.js",
    "content": "// components/player.js\nconst app = getApp()\nimport props from '../utils/js/props.js'\nComponent({\n  /**\n   * 组件的属性列表\n   */\n  properties: {\n    currentTime: {\n      type: Number,\n      value: 0,\n      observer: function (newVal, oldVal, changedPath) {\n        if (newVal>0) {\n          props.setPlayer.call(this, app.player)\n        }\n      }\n    }\n  },\n\n  /**\n   * 组件的初始数据\n   */\n  data: {\n    player:{}\n  },\n  attached(){\n\n  },\n  /**\n   * 组件的方法列表\n   */\n  methods: {\n    pauseAudio() {\n      app.audioDom.pause()\n      props.setPlayer.call(this, { status: 2 })\n      this.triggerEvent('updatePlayer')\n    },\n    playAudio() {\n      app.audioDom.play()\n      props.setPlayer.call(this, { status: 1 })\n      this.triggerEvent('updatePlayer')\n    },\n    playLast() {\n      let current_music = app.player.current_music - 1\n      if (current_music < 0) {\n        current_music = app.player.list.length - 1\n      }\n      this.playCurrent(current_music)\n    },\n    playNext() {\n      let current_music = app.player.current_music + 1\n      if (current_music >= app.player.list.length) {\n        current_music = 0\n      }\n      this.playCurrent(current_music)\n    },\n    playCurrent(current_music) {\n      props.setPlayer.call(this, { current_music, status: 1 })\n      props.setPlaying.call(this, app.player.list[current_music], ()=>{\n        let audioDom = app.audioDom\n        audioDom.title = app.player.playing.name\n        audioDom.src = app.player.a_resource + app.player.playing.url\n        audioDom.coverImgUrl = app.player.i_resource + app.player.playing.cover + '-ph'\n        audioDom.play()\n      }) \n      this.triggerEvent('updatePlayer')\n    },\n    closePlayer() {\n      props.setPlayer.call(this, { global_show: 0 })\n    },\n    toDtail() {\n      let { id } = app.player.playing\n      let url = `/pages/music/detail?id=${id}`\n      wx.navigateTo({\n        url\n      })\n    }\n  }\n})\n"
  },
  {
    "path": "components/player.json",
    "content": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "components/player.wxml",
    "content": "<!--components/player.wxml-->\n<view bindtap='toDtail' wx:if=\"{{player.global_show && player.playing.id}}\" class='player df-j-b'>\n  <view class='avatar'>\n    <image wx:if=\"{{player.i_resource &&  player.playing.cover}}\" style=\"{{player.status === 1 ? 'animation: rotateImg 4s infinite linear' : ''}}\" class='avatar_img' src=\"{{player.i_resource + player.playing.cover+'-cover'}}\"></image>\n  </view>\n  <view class='content df-1 df-j-b'>\n    <view class='info df-col'>\n      <view class='name'>{{player.playing.name}}</view>\n      <view class='singer'>{{player.playing.singer.name}}</view>\n    </view>\n    <view class='right df-j-b'>\n      <view class='controls df-j-b'>\n        <i catchtap=\"playLast\" class='last_next iconfont icon-last-play'></i>\n        <view>\n          <i catchtap=\"pauseAudio\" wx:if=\"{{player.status == 1}}\" class='play_pause iconfont icon-pause'></i>\n          <i catchtap=\"playAudio\" wx:else class='play_pause iconfont icon-play'></i>\n        </view>\n        <i catchtap='playNext' class='last_next iconfont icon-next-play'></i>\n      </view>\n      <view class='delete df'>\n        <view class='line'></view>\n        <i catchtap=\"closePlayer\" class='iconfont icon-error'></i>\n      </view>\n    </view>\n  </view>\n</view>\n"
  },
  {
    "path": "components/player.wxss",
    "content": "/* components/player.wxss */\n@import '/utils/wxss/style.wxss';\n@import '/utils/wxss/icon.wxss';\n.player{\n  width: 90%;\n  padding: 0.2rem 0.5rem;\n  border-radius: 0.3rem;\n  box-sizing: border-box;\n  position: fixed;\n  z-index: 9999;\n  background: #999;\n  bottom: 4rem;\n  left:5%;\n  align-items: center;\n}\n\n.avatar{\n  width: 2rem;\n  height: 2rem;\n  border-radius: 50%;\n  overflow: hidden;\n  margin-right: 0.5rem;\n}\n.avatar_img{\n  width: 2rem;\n  height: 2rem;\n  border-radius: 50%;\n}\n\n.content{\n  color: #fff;\n  font-size: 0.7rem;\n}\n.content .singer{\n  margin-top: 0.2rem;\n  font-size: 0.6rem;\n  color: #d2d2d2;\n}\n.content .right{\n  align-items: center;\n}\n.content .right .controls{\n  align-items: center;\n}\n.content .right .controls .last_next{\n  font-size:1rem;\n}\n.content .right .controls .play_pause{\n  font-size: 1.7rem;\n  margin: 0 0.3rem;\n}\n.content .right .delete{\n  align-items: center;\n  margin-left: 1rem;\n}\n.content .right .delete .line{\n  height: 1.3rem;\n  width: 2px;\n  background: #fff;\n  margin-right: 0.5rem;\n}\n\n@keyframes rotateImg{\n  from{transform: rotate(0);}\n  to{transform: rotate(360deg);}\n}"
  },
  {
    "path": "components/progress.js",
    "content": "// components/progress.js\nconst app = getApp()\nimport props from '../utils/js/props.js'\nComponent({\n  /**\n   * 组件的属性列表\n   */\n  properties: {\n    currentTime: {\n      type: Number,\n      value: 0,\n      observer: function (newVal, oldVal, changedPath) {\n        props.setPlaying.call(this, { currentTime: newVal, duration: app.audioDom.duration })\n        if(!this.data.userChange){\n          this.updateProgress()\n        }\n      }\n    }\n  },\n\n  /**\n   * 组件的初始数据\n   */\n  data: {\n    player:{},\n    left: 0,\n    sliderW: 14,\n    userChange: false,\n\n    swiperWidth: 0,\n    swiperLeft: 0,\n  }, \n  ready() {\n    this.getSwiperInfo()\n  },  \n\n  /**\n   * 组件的方法列表\n   */\n  methods: {\n    getSwiperInfo(){\n      const that = this;\n      let query = wx.createSelectorQuery().in(this)\n      query.select('.swiper').boundingClientRect(function (res) {\n        //console.log(res)\n        that.setData({ swiperWidth: res.width, swiperLeft: res.left })\n      }).exec()\n    },\n    updateProgress(){\n      let { currentTime, duration } = app.player.playing;\n      let left = Math.floor(this.data.swiperWidth * (currentTime / duration))\n      this.setData({ left })\n    },\n    swiperTouch(e) {\n      let swiperWidth = this.data.swiperWidth\n      let startX = this.data.swiperLeft\n      let endX = e.touches[0].clientX\n      this.setData({ left: endX - startX })\n      let position = ((endX - startX) / swiperWidth) * app.audioDom.duration\n      wx.seekBackgroundAudio({\n        position: Math.round(position),\n      })\n    },\n    touchStart(e) {\n      this.setData({ sliderW: 18, userChange: true })\n    },\n    touchMove(e) {\n      let swiperWidth = this.data.swiperWidth\n      let sliderWidth = this.data.sliderW\n      let startX = this.data.swiperLeft\n      let endX = e.touches[0].clientX\n      if (endX - startX <= 0) {\n        this.setData({ left: 0 })\n        return\n      }\n      if (endX - startX >= (swiperWidth - sliderWidth)) {\n        this.setData({ left: swiperWidth - sliderWidth })\n        return\n      }\n      this.setData({ left: endX - startX })\n    },\n    touchEnd() {\n      let swiperWidth = this.data.swiperWidth\n      this.setData({ sliderW: 12, userChange: false })\n      //this.props.changeProgress(this.data.left / swiperWidth)\n      let position = (this.data.left / swiperWidth) * app.audioDom.duration\n      wx.seekBackgroundAudio({\n        position: Math.round(position),\n      })\n    }\n  }\n})\n"
  },
  {
    "path": "components/progress.json",
    "content": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "components/progress.wxml",
    "content": "<!--components/progress.wxml-->\n<view>\n  <view bindtouchstart='swiperTouch' class='swiper'>\n    <view style=\"width: {{left}}px;\" class='line'></view>\n    <view\n      class='slider' \n      style=\"width: {{sliderW}}px; height: {{sliderW}}px; left: {{left}}px; top: {{-(sliderW / 2 -3)}}px\"\n      catchtouchstart='touchStart'\n      bindtouchmove='touchMove'\n      bindtouchend='touchEnd'\n    ></view>\n  </view>\n</view>\n"
  },
  {
    "path": "components/progress.wxss",
    "content": "/* components/progress.wxss */\n.swiper{\n  width: 100%;\n  height: 6px;\n  background: #d2d2d2;\n  margin: 20px 0;\n  position: relative;\n  border-radius: 6px;\n}\n.line{\n  height: 6px;\n  background: #dd5866;\n  position: absolute;\n  left: 0;\n  top: 0;\n  border-radius: 6px 0 0 6px;\n}\n.slider{\n  border-radius: 50%;\n  background: #33c9d4;\n  position: absolute;\n}"
  },
  {
    "path": "pages/music/detail.js",
    "content": "// pages/music/detail.js\nimport props from '../../utils/js/props.js'\nimport common from '../../utils/js/common.js'\nimport ajax from '../../utils/js/ajax.js'\nconst app = getApp()\nlet audioDom = app.audioDom;\nPage({\n\n  /**\n   * 页面的初始数据\n   */\n  data: {\n    player:{},\n    id: '',\n    audioDom:''\n  },\n\n  /**\n   * 生命周期函数--监听页面加载\n   */\n  onLoad: function (options) {\n    let id = options.id || 1\n    if (id == app.player.playing.id){\n      this.setData({ id }, ()=>{\n        this.updateLrc()\n      })\n    }else{\n      wx.stopBackgroundAudio()\n      this.setData({ id: options.id || 1 }, () => {\n        this.getDetail()\n      })\n    }\n  },\n\n  /**\n   * 生命周期函数--监听页面初次渲染完成\n   */\n  onReady: function () {\n\n  },\n\n  /**\n   * 生命周期函数--监听页面显示\n   */\n  onShow: function () {\n    props.setPlayer.call(this, app.player)\n    this.audioWatch()\n  },\n  updeData(current_music){\n    if (current_music !== this.data.current_music) {\n      props.setPlayer.call(this, { current_music })\n      props.setPlaying.call(this, app.player.list[current_music], ()=>{\n        this.playAudio()\n        this.setData({ id: app.player.playing.id }, () => this.updateLrc())\n      })\n    }\n  },\n  getDetail() {\n    ajax.get('audio/detail', { id: this.data.id }).then(res => {\n      if (res.code === 0) {\n        let { a_resource, i_resource } = res.data;\n        let { lrc, id, url, cover, singer, name } = res.data.audio\n        let { timeArr, lrcArr } = common.analysis(lrc)\n        props.setPlaying.call(this, { id, timeArr, lrcArr, url, singer, name, cover })\n        props.setPlayer.call(this, { i_resource, a_resource, global_show: 1, status: 1 })\n\n        this.playAudio()\n      }\n    })\n  },\n  updateLrc() {\n    ajax.get('lrc', { id: this.data.id }).then(res => {\n      if (res.code === 0) {\n        let lrc = res.data.lrc\n        let { timeArr, lrcArr } = common.analysis(lrc)\n        props.setPlaying.call(this, { timeArr, lrcArr })\n      }\n    })\n  },\n  audioWatch(){\n    let audioDom = app.audioDom\n    audioDom.onPlay(() => {\n      props.setPlayer.call(this, { status: 1 })\n      props.setPlaying.call(this, { duration: audioDom })\n    })\n    audioDom.onPause(()=>{\n      props.setPlayer.call(this, { status: 2 })\n    })\n    audioDom.onTimeUpdate(() => {\n      props.setPlaying.call(this, { currentTime: audioDom.currentTime })\n    })\n    audioDom.onEnded(() => {\n      // 安卓监听不到。。。，并没有问题，是将接口使用错误了\n      this.audioEnded()\n    })\n  },\n  audioEnded(){\n    let player = app.player, playing = app.player.playing;\n    let mode = app.player.mode;\n    switch (mode) {  // 0 单曲    1 顺序   2 随机\n      case 0:\n        console.log('单曲模式')\n        this.playAudio()\n        break;\n      case 1:\n        if (player.list.length > 0) {\n          console.log('列表模式下一曲')\n          let current_music = player.current_music + 1\n          if (current_music >= player.list.length) {\n            current_music = 0;\n          }\n          this.updeData(current_music)\n          return\n        }\n        app.audioDom.pause()\n        break;\n      case 2:\n        if (player.list.length > 0) {\n          console.log('随机模式下一曲')\n          let current_music = Math.floor(Math.random() * player.list.length)\n          this.updeData(current_music)\n          return\n        }\n        app.audioDom.pause()\n        break;\n      default:\n        app.audioDom.pause()\n    }\n  },\n  playAudio(){\n    let audioDom = app.audioDom\n    wx.setNavigationBarTitle({\n      title: app.player.playing.name\n    })\n    audioDom.title = app.player.playing.name\n    audioDom.src = app.player.a_resource + app.player.playing.url\n    audioDom.coverImgUrl = app.player.i_resource + app.player.playing.cover + '-ph'\n    audioDom.play()\n  },\n  playLast() {\n    let current_music = app.player.current_music - 1\n    if (current_music < 0) {\n      current_music = app.player.list.length - 1\n    }\n    props.setPlayer.call(this, { current_music })\n    this.setData({ current_music }, () => {\n      let id = app.player.list[current_music].id\n      this.setData({ id }, () => this.getDetail())\n    })\n  },\n  playNext() {\n    let current_music = app.player.current_music + 1\n    if (current_music >= app.player.list.length) {\n      current_music = 0\n    }\n    props.setPlayer.call(this, { current_music })\n    this.setData({ current_music }, () => {\n      let id = app.player.list[current_music].id\n      this.setData({ id }, () => this.getDetail())\n    })\n  },\n  /**\n   * 生命周期函数--监听页面隐藏\n   */\n  onHide: function () {\n\n  },\n\n  /**\n   * 生命周期函数--监听页面卸载\n   */\n  onUnload: function () {\n\n  },\n\n  /**\n   * 页面相关事件处理函数--监听用户下拉动作\n   */\n  onPullDownRefresh: function () {\n\n  },\n\n  /**\n   * 页面上拉触底事件的处理函数\n   */\n  onReachBottom: function () {\n\n  },\n\n  /**\n   * 用户点击右上角分享\n   */\n  onShareAppMessage: function () {\n\n  }\n})"
  },
  {
    "path": "pages/music/detail.json",
    "content": "{\n  \"usingComponents\": {\n    \"lrc\": \"/components/lrc\",\n    \"controls\": \"/components/controls\"\n  }\n}"
  },
  {
    "path": "pages/music/detail.wxml",
    "content": "<!--pages/music/detail.wxml-->\n<view wx:if=\"{{player.i_resource && player.playing.cover}}\" style=\"background:url({{player.i_resource + player.playing.cover + '-ph'}}) 0 0 / cover;\" class='detail'>\n  <view class='detail_container'>\n    <lrc currentTime=\"{{player.playing.currentTime}}\"></lrc>\n    <controls currentTime=\"{{player.playing.currentTime}}\" bind:playLast=\"playLast\" bind:playNext=\"playNext\"></controls>\n  </view>\n</view>\n\n"
  },
  {
    "path": "pages/music/detail.wxss",
    "content": "/* pages/music/detail.wxss */\n.detail{\n  width: 100%;\n  height: 100vh;\n  overflow: hidden;\n}\n.detail_container{\n  width: 100%;\n  height: 100vh;\n  position: fixed;\n  top:0;\n  left: 0;\n  background: rgba(0,0,0,0.25);\n}"
  },
  {
    "path": "pages/music/list.js",
    "content": "// pages/music/list.js\nconst app = getApp()\nconst dayjs = require('../../utils/js/dayjs.js')\nimport ajax from '../../utils/js/ajax.js'\nimport props from '../../utils/js/props.js'\nPage({\n\n  /**\n   * 页面的初始数据\n   */\n  data: {\n    player:{},\n    musics:[\n      { audios: [], page: {} },   // 推荐\n      { audios: [], page: {} },   // 最热\n      { audios: [], page: {} },   // 原创\n      { audios: [], page: {} },   // 飙升\n      { audios: [], page: {} },   // 最新\n    ],\n    types: ['推荐', '最热', '原创', '飙升', '最新'],\n    currentType: 0,\n    swiperHeight: 0,\n    startX: 0,\n    endX: 0,\n    lineWidth: 0,\n    lineLeft: 0,\n    loadingArr: [0, 0, 0, 0, 0],\n    p: 1\n  },\n\n  /**\n   * 生命周期函数--监听页面加载\n   */\n  onLoad: function (options) {\n    this.getList()\n  }, \n\n  /**\n   * 生命周期函数--监听页面初次渲染完成\n   */\n  onReady: function () {\n\n  },\n\n  /**\n   * 生命周期函数--监听页面显示\n   */\n  onShow: function () {\n    this.updatePlayer()\n  },\n  updatePlayer(){\n    props.setPlayer.call(this, app.player)\n  },\n  getList(){\n    const type = this.data.currentType;\n    let loadingArr = this.data.loadingArr\n    loadingArr[type] = 1\n    this.setData({ loadingArr })\n    ajax.get('audio', { type, p: this.data.p }).then(res => {\n      loadingArr[type] = 0\n      this.setData({ loadingArr })\n      if (res.code === 0) {\n        let { audios, i_resource, a_resource, page } = res.data\n        this.saveMusics({ type, page, audios }, ()=>{\n          this.getSwiperHeight((swiperHeight) => this.setData({ swiperHeight }))\n        })\n        props.setPlayer.call(this, { i_resource, a_resource })\n        props.setPlayer.call(this, { list: this.data.musics[type].audios })\n      }\n    })\n  },\n  saveMusics(data, cb){\n    let { type, page, audios } = data;\n    let musics = this.data.musics\n    musics[type].audios = musics[type].audios.concat(audios)\n    musics[type].page = page\n    this.setData({ musics }, ()=>{\n      cb && cb()\n    })\n  },\n  dateFormat(t){\n    return dayjs(t * 1000).format('YYYY.MM.DD')\n  },\n  changeAudio(e){\n    let currentType = e.currentTarget.dataset.k\n    this.setData({ currentType })\n  },\n  handleChangeIndex(e) {\n    let currentType = e.detail.current\n    let p = this.data.musics[currentType].page.p || 1\n    props.setPlayer.call(this, { list: this.data.musics[currentType].audios })\n    //console.log(app.player.list)\n    this.setData({ p, currentType }, () => {\n      if (this.data.musics[currentType].audios.length === 0) {\n        this.getList()\n      }\n      this.getSwiperHeight((swiperHeight) => this.setData({ swiperHeight }))\n    })\n  },\n  getSwiperHeight(cb) {\n    var query = wx.createSelectorQuery().in(this)\n    query.select('.swiperShow').boundingClientRect(function (res) {\n      cb && cb(res.height)\n    }).exec()\n  },\n  toDetail(event){\n    let { e, i } = event.currentTarget.dataset\n    props.setPlayer.call(this, { current_music: i })\n    wx.navigateTo({\n      url: `/pages/music/detail?id=${e.id}`\n    })\n  },\n  handleTouchStart(e) {\n    let startX = e.touches[0].clientX\n    this.setData({ startX })\n  },\n  handleTouchMove(e) {\n    let endX = e.touches[0].clientX\n    if (endX - this.data.startX > 0) {\n      this.setData({ lineLeft: (endX - this.data.startX) / 10, lineWidth: (endX - this.data.startX) / 10 })\n    } else {\n      this.setData({ lineWidth: (this.data.startX - endX) / 10 })\n    }\n  },\n  handleTouchEnd(e) {\n    this.setData({ lineWidth: 0, lineLeft: 0 })\n  },\n  /**\n   * 生命周期函数--监听页面隐藏\n   */\n  onHide: function () {\n    \n  },\n\n  /**\n   * 生命周期函数--监听页面卸载\n   */\n  onUnload: function () {\n    \n  },\n\n  /**\n   * 页面相关事件处理函数--监听用户下拉动作\n   */\n  onPullDownRefresh: function () {\n\n  },\n\n  /**\n   * 页面上拉触底事件的处理函数\n   */\n  onReachBottom: function () {\n    let currentType = this.data.currentType\n    if (this.data.loadingArr[currentType] !== 0) return\n    if (this.data.musics[currentType].page.p >= this.data.musics[currentType].page.total_page) {\n      let loadingArr = this.data.loadingArr\n      loadingArr[currentType] = 2\n      this.setData({ loadingArr })\n      return\n    }\n    let p = this.data.musics[currentType].page.p + 1\n    this.setData({ p }, ()=>{\n      this.getList()\n    })\n  },\n\n  /**\n   * 用户点击右上角分享\n   */\n  onShareAppMessage: function () {\n\n  }\n})"
  },
  {
    "path": "pages/music/list.json",
    "content": "{\n  \"usingComponents\": {\n    \"loadmore\": \"/components/loadmore\",\n    \"playerBar\": \"/components/player\"\n  }\n}"
  },
  {
    "path": "pages/music/list.wxml",
    "content": "<!--pages/music/list.wxml-->\n<wxs module=\"time\" src=\"../../utils/wxs/time.wxs\"></wxs>\n\n<view>\n  <view class='typeTitle'>\n    <view class='typeList df-j-b'>\n      <block wx:for=\"{{types}}\" wx:for-item='v' wx:for-index='k' wx:key='k'>\n        <view bindtap='changeAudio' data-k=\"{{k}}\" class=\"{{k===currentType ? 'itemShow' : ''}} typeItem\">{{v}}</view>\n      </block>\n    </view>\n    <view class='navSwiper'>\n      <view style=\"left:{{currentType * 20}}%\" class='navSlider'>\n        <view \n        style=\"width:calc(60% + {{lineWidth}}px); margin-left:calc(20% - {{lineLeft}}px); transition: {{lineWidth > 0 ? '' : 'all 0.5s'}};\" \n        class='navLine'></view>\n      </view>\n    </view>\n  </view>\n\n  <swiper\n    circular=\"{{true}}\"\n    current=\"{{currentType}}\"\n    style=\"width:100%;height:{{swiperHeight}}px;\"\n    bindchange='handleChangeIndex'\n  >\n    <block wx:for=\"{{musics}}\" wx:for-item='v' wx:for-index='k' wx:key='k'>\n      <swiper-item>\n        <view bindtouchstart='handleTouchStart' bindtouchmove='handleTouchMove' bindtouchend='handleTouchEnd' class=\"{{currentType === k ? 'swiperShow' : 'swiper'}}\">\n          <block wx:for=\"{{v.audios}}\" wx:for-item=\"e\" wx:for-index=\"i\" wx:key=\"i\">\n            <view bindtap='toDetail' data-e=\"{{e}}\" data-i=\"{{i}}\" class=\"item df {{player.playing.id === e.id && player.status === 1? 'item_show' : ''}}\">\n              <image wx:if=\"{{player.i_resource}}\" class='item_cover' src=\"{{player.i_resource + e.cover + '-cover'}}\"></image>\n              <view class='item_info df-1'>\n                <view class='item_name'>{{e.name}}</view>\n                <view class='singer_name df'>\n                  <view class='singer_avatar'>\n                    <image wx:if=\"{{player.i_resource}}\" class='avatar_img' src=\"{{player.i_resource + e.singer.avatar + '-avatar'}}\"></image>\n                  </view>\n                  {{e.singer.name}}\n                </view>\n                <view class='item_date'>{{time.dateFormat(e.date)}}</view>\n              </view>\n            </view>\n          </block>\n        </view>\n      </swiper-item>\n    </block>\n  </swiper>\n\n  <loadmore class=\"component-container\" loading='{{loadingArr[currentType]}}'></loadmore>\n  <playerBar currentTime=\"{{player.playing.currentTime}}\" bind:updatePlayer=\"updatePlayer\"></playerBar>\n</view>\n"
  },
  {
    "path": "pages/music/list.wxss",
    "content": "/* pages/music/list.wxss */\n.typeTitle{\n  width: 100%;\n  position: fixed;\n  left: 0;\n  top: 0;\n  z-index: 999;\n  background: #fff;\n  border-bottom: 1px solid #eee;\n}\n.typeList{\n  height: 1.8rem;\n  padding-bottom: 0.4rem;\n  font-size: 0.7rem;\n  color: #333;\n  align-items: flex-end;\n}\n.typeItem{\n  width: 20%;\n  transition: all 0.3s;\n  text-align: center;\n}\n.itemShow{\n  font-size: 0.9rem;\n  color: #33c9d4;\n}\n\n.navSwiper{\n  position: relative;\n}\n.navSlider{\n  width: 20%;\n  height: 2px;\n  position: absolute;\n  bottom: -1px;\n  transition: all 0.5s;\n}\n.navLine{\n  height: 100%;\n  background: #33c9d4;\n  width: 60%;\n  margin-left: 20%;\n}\n\n\n.swiper{\n  width: 100%;\n  padding-top: 2rem;\n  padding-bottom: 3rem;\n}\n.swiperShow{\n  width: 100%;\n  padding-top: 2rem;\n  padding-bottom: 3rem;\n  max-height: auto;\n}\n\n.item{\n  color: #333;\n  padding: 1rem 0.75rem 0.5rem 0.75rem;\n  border-top: 1px solid #eee;\n  align-items: center;\n}\n.swiper .item:first-child, .swiperShow .item:first-child{\n  border: none;\n}\n.item .item_cover{\n  width: 4rem;\n  height: 4rem;\n  border-radius: 50%;\n}\n.item_show{\n  color: #fff;\n  background: url('http://audio.22family.com//gif/playing.gif') 0 0 /cover;\n}\n.item .item_info{\n  width: 100%;\n  min-height: 4rem;\n  padding: 0.5rem 0;\n  padding-left: 1rem;\n}\n.item .item_info .item_name{\n  font-size: 0.8rem;\n  align-self: flex-start;\n}\n.item .item_info .singer_name{\n  align-items: center;\n  padding: 0.7rem 0 0.3rem 0;\n  font-size: 0.7rem;\n  color: #666;\n}\n.item .item_info .singer_name .singer_avatar{\n  width: 1.4rem;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  margin-right: 0.4rem;\n}\n.item .item_info .singer_name .avatar_img{\n  width: 1rem;\n  height:1rem;\n  border-radius: 50%;\n}\n.item .item_info .item_date{\n  font-size: 0.6rem;\n  color: #999;\n}"
  },
  {
    "path": "project.config.json",
    "content": "{\n\t\"description\": \"项目配置文件\",\n\t\"packOptions\": {\n\t\t\"ignore\": []\n\t},\n\t\"setting\": {\n\t\t\"urlCheck\": true,\n\t\t\"es6\": true,\n\t\t\"postcss\": true,\n\t\t\"minified\": true,\n\t\t\"newFeature\": true,\n\t\t\"autoAudits\": false\n\t},\n\t\"compileType\": \"miniprogram\",\n\t\"libVersion\": \"2.6.2\",\n\t\"appid\": \"wx7d3aad6d394ff0ab\",\n\t\"projectname\": \"mySkey\",\n\t\"debugOptions\": {\n\t\t\"hidedInDevtools\": []\n\t},\n\t\"isGameTourist\": false,\n\t\"condition\": {\n\t\t\"search\": {\n\t\t\t\"current\": -1,\n\t\t\t\"list\": []\n\t\t},\n\t\t\"conversation\": {\n\t\t\t\"current\": -1,\n\t\t\t\"list\": []\n\t\t},\n\t\t\"plugin\": {\n\t\t\t\"current\": -1,\n\t\t\t\"list\": []\n\t\t},\n\t\t\"game\": {\n\t\t\t\"currentL\": -1,\n\t\t\t\"list\": []\n\t\t},\n\t\t\"miniprogram\": {\n\t\t\t\"current\": 0,\n\t\t\t\"list\": [\n\t\t\t\t{\n\t\t\t\t\t\"id\": -1,\n\t\t\t\t\t\"name\": \"播放页\",\n\t\t\t\t\t\"pathName\": \"pages/music/detail\",\n\t\t\t\t\t\"query\": \"id=3\",\n\t\t\t\t\t\"scene\": null\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t}\n}"
  },
  {
    "path": "utils/js/ajax.js",
    "content": "let api_url = 'https://www.22family.com/music/test/'\nexport default{\n  get(url, data){\n    return new Promise((resolve, reject)=>{\n      wx.request({\n        url: api_url + url,\n        method: 'GET',\n        data,\n        header: {\n          'content-type': 'application/json' // 默认值\n        },\n        success(res) {\n          resolve(res.data)\n        },\n        error(err){\n          reject(err)\n        }\n      })\n    })\n  },\n  post(url, data) {\n    return new Promise((resolve, reject) => {\n      wx.request({\n        url: api_url + url,\n        method: 'POST',\n        data,\n        header: {\n          'content-type': 'application/json' // 默认值\n        },\n        success(res) {\n          resolve(res.data)\n        },\n        error(err) {\n          reject(err)\n        }\n      })\n    })\n  }\n}"
  },
  {
    "path": "utils/js/audio.js",
    "content": ""
  },
  {
    "path": "utils/js/common.js",
    "content": "export default {\n  toast(title, duration = 1500){\n    wx.showToast({\n      title,\n      icon: 'none',\n      duration\n    })\n  },\n  confirm(content, cb){\n    wx.showModal({\n      title: '温馨提示',\n      content,\n      success(res) {\n        if (res.confirm) {\n          cb && cb()\n        }\n      }\n    })\n  },\n  showLoading(title){\n    wx.showLoading({\n      title\n    })\n  },\n  hideLoading(){\n    wx.hideLoading()\n  },\n  analysis(str) {\n    str = str.slice(str.indexOf('00:00.00'))\n    let s = str.replace(/[\\s\\r\\n]/g, \"\").split('[');\n    let timeArr = [], lrcArr = [];\n    for (let v of s) {\n      let lrc = v.split(']')\n      timeArr.push(lrc[0])\n      lrcArr.push(lrc[1])\n    }\n    return { timeArr, lrcArr }\n  },\n  getAudioTime(num = 0) {\n    let minute = Math.floor(num / 60).toString();\n    let second = Math.floor(num % 60).toString();\n    return `${minute.padStart(2, '0')} : ${second.padStart(2, '0')}`\n  },\n  getSecond(t) {\n    let minute = Number(t.slice(0, 2))\n    let second = Number(t.slice(3, 5))\n    let minS = Number(t.slice(7))\n    return minS > 100 ? (minute * 60 + second + minS / 1000) : (minute * 60 + second + minS / 100)\n  },\n}"
  },
  {
    "path": "utils/js/dayjs.js",
    "content": "!function (t, e) { \"object\" == typeof exports && \"undefined\" != typeof module ? module.exports = e() : \"function\" == typeof define && define.amd ? define(e) : t.dayjs = e() }(this, function () { \"use strict\"; var t = \"millisecond\", e = \"second\", n = \"minute\", r = \"hour\", s = \"day\", i = \"week\", a = \"month\", u = \"year\", c = /^(\\d{4})-?(\\d{1,2})-?(\\d{0,2})(.*?(\\d{1,2}):(\\d{1,2}):(\\d{1,2}))?.?(\\d{1,3})?$/, o = /\\[.*?\\]|Y{2,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, h = { name: \"en\", weekdays: \"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday\".split(\"_\"), months: \"January_February_March_April_May_June_July_August_September_October_November_December\".split(\"_\") }, d = function (t, e, n) { var r = String(t); return !r || r.length >= e ? t : \"\" + Array(e + 1 - r.length).join(n) + t }, f = { padStart: d, padZoneStr: function (t) { var e = Math.abs(t), n = Math.floor(e / 60), r = e % 60; return (t <= 0 ? \"+\" : \"-\") + d(n, 2, \"0\") + \":\" + d(r, 2, \"0\") }, monthDiff: function (t, e) { var n = 12 * (e.year() - t.year()) + (e.month() - t.month()), r = t.clone().add(n, \"months\"), s = e - r < 0, i = t.clone().add(n + (s ? -1 : 1), \"months\"); return Number(-(n + (e - r) / (s ? r - i : i - r))) }, absFloor: function (t) { return t < 0 ? Math.ceil(t) || 0 : Math.floor(t) }, prettyUnit: function (c) { return { M: a, y: u, w: i, d: s, h: r, m: n, s: e, ms: t }[c] || String(c || \"\").toLowerCase().replace(/s$/, \"\") }, isUndefined: function (t) { return void 0 === t } }, $ = \"en\", l = {}; l[$] = h; var m = function (t) { return t instanceof D }, y = function (t, e, n) { var r; if (!t) return null; if (\"string\" == typeof t) l[t] && (r = t), e && (l[t] = e, r = t); else { var s = t.name; l[s] = t, r = s } return n || ($ = r), r }, M = function (t, e) { if (m(t)) return t.clone(); var n = e || {}; return n.date = t, new D(n) }, S = function (t, e) { return M(t, { locale: e.$L }) }, p = f; p.parseLocale = y, p.isDayjs = m, p.wrapper = S; var D = function () { function h(t) { this.parse(t) } var d = h.prototype; return d.parse = function (t) { var e, n; this.$d = null === (e = t.date) ? new Date(NaN) : p.isUndefined(e) ? new Date : e instanceof Date ? e : \"string\" == typeof e && /.*[^Z]$/i.test(e) && (n = e.match(c)) ? new Date(n[1], n[2] - 1, n[3] || 1, n[5] || 0, n[6] || 0, n[7] || 0, n[8] || 0) : new Date(e), this.init(t) }, d.init = function (t) { this.$y = this.$d.getFullYear(), this.$M = this.$d.getMonth(), this.$D = this.$d.getDate(), this.$W = this.$d.getDay(), this.$H = this.$d.getHours(), this.$m = this.$d.getMinutes(), this.$s = this.$d.getSeconds(), this.$ms = this.$d.getMilliseconds(), this.$L = this.$L || y(t.locale, null, !0) || $ }, d.$utils = function () { return p }, d.isValid = function () { return !(\"Invalid Date\" === this.$d.toString()) }, d.$compare = function (t) { return this.valueOf() - M(t).valueOf() }, d.isSame = function (t) { return 0 === this.$compare(t) }, d.isBefore = function (t) { return this.$compare(t) < 0 }, d.isAfter = function (t) { return this.$compare(t) > 0 }, d.year = function () { return this.$y }, d.month = function () { return this.$M }, d.day = function () { return this.$W }, d.date = function () { return this.$D }, d.hour = function () { return this.$H }, d.minute = function () { return this.$m }, d.second = function () { return this.$s }, d.millisecond = function () { return this.$ms }, d.unix = function () { return Math.floor(this.valueOf() / 1e3) }, d.valueOf = function () { return this.$d.getTime() }, d.startOf = function (t, c) { var o = this, h = !!p.isUndefined(c) || c, d = function (t, e) { var n = S(new Date(o.$y, e, t), o); return h ? n : n.endOf(s) }, f = function (t, e) { return S(o.toDate()[t].apply(o.toDate(), h ? [0, 0, 0, 0].slice(e) : [23, 59, 59, 999].slice(e)), o) }; switch (p.prettyUnit(t)) { case u: return h ? d(1, 0) : d(31, 11); case a: return h ? d(1, this.$M) : d(0, this.$M + 1); case i: return d(h ? this.$D - this.$W : this.$D + (6 - this.$W), this.$M); case s: case \"date\": return f(\"setHours\", 0); case r: return f(\"setMinutes\", 1); case n: return f(\"setSeconds\", 2); case e: return f(\"setMilliseconds\", 3); default: return this.clone() } }, d.endOf = function (t) { return this.startOf(t, !1) }, d.$set = function (i, c) { switch (p.prettyUnit(i)) { case s: this.$d.setDate(this.$D + (c - this.$W)); break; case \"date\": this.$d.setDate(c); break; case a: this.$d.setMonth(c); break; case u: this.$d.setFullYear(c); break; case r: this.$d.setHours(c); break; case n: this.$d.setMinutes(c); break; case e: this.$d.setSeconds(c); break; case t: this.$d.setMilliseconds(c) }return this.init(), this }, d.set = function (t, e) { return this.clone().$set(t, e) }, d.add = function (t, c) { var o = this; t = Number(t); var h, d = p.prettyUnit(c), f = function (e, n) { var r = o.set(\"date\", 1).set(e, n + t); return r.set(\"date\", Math.min(o.$D, r.daysInMonth())) }, $ = function (e) { var n = new Date(o.$d); return n.setDate(n.getDate() + e * t), S(n, o) }; if (d === a) return f(a, this.$M); if (d === u) return f(u, this.$y); if (d === s) return $(1); if (d === i) return $(7); switch (d) { case n: h = 6e4; break; case r: h = 36e5; break; case e: h = 1e3; break; default: h = 1 }var l = this.valueOf() + t * h; return S(l, this) }, d.subtract = function (t, e) { return this.add(-1 * t, e) }, d.format = function (t) { var e = this, n = t || \"YYYY-MM-DDTHH:mm:ssZ\", r = p.padZoneStr(this.$d.getTimezoneOffset()), s = this.$locale(), i = s.weekdays, a = s.months, u = function (t, e, n, r) { return t && t[e] || n[e].substr(0, r) }; return n.replace(o, function (t) { if (t.indexOf(\"[\") > -1) return t.replace(/\\[|\\]/g, \"\"); switch (t) { case \"YY\": return String(e.$y).slice(-2); case \"YYYY\": return String(e.$y); case \"M\": return String(e.$M + 1); case \"MM\": return p.padStart(e.$M + 1, 2, \"0\"); case \"MMM\": return u(s.monthsShort, e.$M, a, 3); case \"MMMM\": return a[e.$M]; case \"D\": return String(e.$D); case \"DD\": return p.padStart(e.$D, 2, \"0\"); case \"d\": return String(e.$W); case \"dd\": return u(s.weekdaysMin, e.$W, i, 2); case \"ddd\": return u(s.weekdaysShort, e.$W, i, 3); case \"dddd\": return i[e.$W]; case \"H\": return String(e.$H); case \"HH\": return p.padStart(e.$H, 2, \"0\"); case \"h\": case \"hh\": return 0 === e.$H ? 12 : p.padStart(e.$H < 13 ? e.$H : e.$H - 12, \"hh\" === t ? 2 : 1, \"0\"); case \"a\": return e.$H < 12 ? \"am\" : \"pm\"; case \"A\": return e.$H < 12 ? \"AM\" : \"PM\"; case \"m\": return String(e.$m); case \"mm\": return p.padStart(e.$m, 2, \"0\"); case \"s\": return String(e.$s); case \"ss\": return p.padStart(e.$s, 2, \"0\"); case \"SSS\": return p.padStart(e.$ms, 3, \"0\"); case \"Z\": return r; default: return r.replace(\":\", \"\") } }) }, d.diff = function (t, c, o) { var h = p.prettyUnit(c), d = M(t), f = this - d, $ = p.monthDiff(this, d); switch (h) { case u: $ /= 12; break; case a: break; case \"quarter\": $ /= 3; break; case i: $ = f / 6048e5; break; case s: $ = f / 864e5; break; case r: $ = f / 36e5; break; case n: $ = f / 6e4; break; case e: $ = f / 1e3; break; default: $ = f }return o ? $ : p.absFloor($) }, d.daysInMonth = function () { return this.endOf(a).$D }, d.$locale = function () { return l[this.$L] }, d.locale = function (t, e) { var n = this.clone(); return n.$L = y(t, e, !0), n }, d.clone = function () { return S(this.toDate(), this) }, d.toDate = function () { return new Date(this.$d) }, d.toArray = function () { return [this.$y, this.$M, this.$D, this.$H, this.$m, this.$s, this.$ms] }, d.toJSON = function () { return this.toISOString() }, d.toISOString = function () { return this.toDate().toISOString() }, d.toObject = function () { return { years: this.$y, months: this.$M, date: this.$D, hours: this.$H, minutes: this.$m, seconds: this.$s, milliseconds: this.$ms } }, d.toString = function () { return this.$d.toUTCString() }, h }(); return M.extend = function (t, e) { return t(e, D, M), M }, M.locale = y, M.isDayjs = m, M.unix = function (t) { return M(1e3 * t) }, M.en = l[$], M });"
  },
  {
    "path": "utils/js/props.js",
    "content": "const app = getApp()\nexport default class Props{\n  static setPlayer(player, cb=null){\n    app.player = Object.assign({}, app.player, player)\n    this.setData({ player: app.player }, ()=>{\n      cb && cb()\n    })\n  }\n  static setPlaying(playing, cb=null){\n    app.player.playing = Object.assign({}, app.player.playing, playing)\n    this.setData({ player: app.player }, ()=>{\n      cb && cb()\n    })\n  }\n}"
  },
  {
    "path": "utils/wxs/time.wxs",
    "content": "// wxs中只支持到es4的语法，所以别使用es6\nmodule.exports = {\n  getAudioTime: function(num) {\n    num = Number(num)\n    var minute = Math.floor(num / 60)\n    var second = Math.floor(num % 60)\n    return ( minute>9 ? minute : '0'+minute) + ' : ' + (second>9 ? second : '0'+second)\n  },\n  dateFormat: function(t) {\n    var date = getDate(t * 1000)\n    var year = date.getFullYear()\n    var month = date.getMonth() + 1\n    var day = date.getDate()\n    return year + '.' + (month>9 ? month : '0' + month) + '.' + (day>9 ? day : '0' + day)\n  }\n}"
  },
  {
    "path": "utils/wxss/icon.wxss",
    "content": "@font-face {font-family: \"iconfont\";\n  src: url('//at.alicdn.com/t/font_1101476_zl0zw7wq9p8.eot?t=1553943371044'); /* IE9 */\n  src: url('//at.alicdn.com/t/font_1101476_zl0zw7wq9p8.eot?t=1553943371044#iefix') format('embedded-opentype'), /* IE6-IE8 */\n  url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),\n  url('//at.alicdn.com/t/font_1101476_zl0zw7wq9p8.woff?t=1553943371044') format('woff'),\n  url('//at.alicdn.com/t/font_1101476_zl0zw7wq9p8.ttf?t=1553943371044') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */\n  url('//at.alicdn.com/t/font_1101476_zl0zw7wq9p8.svg?t=1553943371044#iconfont') format('svg'); /* iOS 4.1- */\n}\n\n.iconfont {\n  font-family: \"iconfont\" !important;\n  font-size: 16px;\n  font-style: normal;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.icon-news:before {\n  content: \"\\e63d\";\n}\n\n.icon-last-play:before {\n  content: \"\\e60b\";\n}\n\n.icon-next-play:before {\n  content: \"\\e611\";\n}\n\n.icon-music:before {\n  content: \"\\e794\";\n}\n\n.icon-menu:before {\n  content: \"\\e62e\";\n}\n\n.icon-only:before {\n  content: \"\\e607\";\n}\n\n.icon-error:before {\n  content: \"\\e766\";\n}\n\n.icon-loading:before {\n  content: \"\\e65d\";\n}\n\n.icon-suiji:before {\n  content: \"\\e802\";\n}\n\n.icon-next:before {\n  content: \"\\e60f\";\n}\n\n.icon-fm:before {\n  content: \"\\e65b\";\n}\n\n.icon-pause:before {\n  content: \"\\e620\";\n}\n\n.icon-play:before {\n  content: \"\\e621\";\n}\n"
  },
  {
    "path": "utils/wxss/reset.wxss",
    "content": "html, body, div, span, applet, object, iframe,\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\na, abbr, acronym, address, big, cite, code,\ndel, dfn, em, img, ins, kbd, q, s, samp,\nsmall, strike, strong, sub, sup, tt, var,\nb, u, i, center,\ndl, dt, dd, ol, ul, li,\nfieldset, form, label, legend,\ntable, caption, tbody, tfoot, thead, tr, th, td,\narticle, aside, canvas, details, embed, \nfigure, figcaption, footer, header, hgroup, \nmenu, nav, output, ruby, section, summary,\ntime, mark, audio, video {\n  margin: 0;\n  padding: 0;\n  border: 0;\n  font-size: 100%;\n  font: inherit;\n  vertical-align: baseline;\n}\n/* HTML5 display-role reset for older browsers */\narticle, aside, details, figcaption, figure, \nfooter, header, hgroup, menu, nav, section {\n  display: block;\n}\nbody {\n  line-height: 1;\n}\nol, ul {\n  list-style: none;\n}\nblockquote, q {\n  quotes: none;\n}\nblockquote:before, blockquote:after,\nq:before, q:after {\n  content: '';\n  content: none;\n}\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n"
  },
  {
    "path": "utils/wxss/style.wxss",
    "content": "html{\n  font-size: 7.3333333333vw;\n  background: #fff;\n  -moz-user-select: none; /*火狐*/\n  -webkit-user-select: none;  /*webkit浏览器*/\n  -ms-user-select: none;   /*IE10*/\n  -khtml-user-select: none; /*早期浏览器*/\n  user-select: none;\n  margin: 0;\n  padding: 0;\n}\nbody {\n  margin: 0;\n  padding: 0;\n  font-family: sans-serif;\n}\nview{\n  margin: 0;\n  padding: 0;\n}\nul>li{\n  list-style: none;\n}\n.df{\n  display: flex;\n}\n.df-1{\n  flex: 1;\n}\n.df-col{\n  display: flex;\n  flex-direction: column;\n}\n.df-j-b{\n  display: flex;\n  justify-content: space-between;\n}\n.df-j-c{\n  display: flex;\n  justify-content: center;\n}"
  }
]