Full Code of mySkey/music-small for AI

master a21a9238173a cached
42 files
51.7 KB
18.8k tokens
66 symbols
1 requests
Download .txt
Repository: mySkey/music-small
Branch: master
Commit: a21a9238173a
Files: 42
Total size: 51.7 KB

Directory structure:
gitextract_ec2v8ip_/

├── README.md
├── app.js
├── app.json
├── app.wxss
├── components/
│   ├── controls.js
│   ├── controls.json
│   ├── controls.wxml
│   ├── controls.wxss
│   ├── loadmore.js
│   ├── loadmore.json
│   ├── loadmore.wxml
│   ├── loadmore.wxss
│   ├── lrc.js
│   ├── lrc.json
│   ├── lrc.wxml
│   ├── lrc.wxss
│   ├── player.js
│   ├── player.json
│   ├── player.wxml
│   ├── player.wxss
│   ├── progress.js
│   ├── progress.json
│   ├── progress.wxml
│   └── progress.wxss
├── pages/
│   └── music/
│       ├── detail.js
│       ├── detail.json
│       ├── detail.wxml
│       ├── detail.wxss
│       ├── list.js
│       ├── list.json
│       ├── list.wxml
│       └── list.wxss
├── project.config.json
└── utils/
    ├── js/
    │   ├── ajax.js
    │   ├── audio.js
    │   ├── common.js
    │   ├── dayjs.js
    │   └── props.js
    ├── wxs/
    │   └── time.wxs
    └── wxss/
        ├── icon.wxss
        ├── reset.wxss
        └── style.wxss

================================================
FILE CONTENTS
================================================

================================================
FILE: README.md
================================================
### 扫码预览

<div align=center><img src="http://img.22family.com/mySKey/small.jpg" /></div>

### 一、需求分析

* 基本功能

上一曲、下一曲、播放、暂停、列表播放

* 切换到别的页面播放继续

* 歌词同步

1、歌词随音乐进度更新
2、拖动歌词后出现可调节位置的控件,此时歌词滚动暂停
3、点击控件播放,关闭控件,改变进度
4、不点击,5s后关闭控件

* 进度条播放

可点击或者拖动进度条来改变歌曲进度

* 播放模式

单曲、列表、随机三种播放模式

* 倍速

可在不同倍速下播放,并且不同倍速下歌词同步


### 其中的经验:

* 1、wxs支持es4的语法
* 2、在app.json中设置"requiredBackgroundModes": ["audio"],将开启后台播放
* 3、wx.getBackgroundAudioManager()的onEnded监听在安卓机上面不生效
* 4、不支持倍速

================================================
FILE: app.js
================================================
//app.js
App({
  onLaunch: function () {
    wx.setNavigationBarTitle({
      title: 'mySkey音乐'
    })
    this.initAudio()
  },
  onShow(){
    if(this.audioDom.paused){
      this.player.status = 2;
    }
  },
  onHide: function () {
    
  },
  player: {
    list: [],
    current_music: 0,
    mode: 0,            // 0 单曲    1 顺序   2 随机
    status: 0,          // 0 未播放  1 播放   2 暂停中  3 已结束
    playbackRate: 1,    // 倍速
    a_resource: '',
    i_resource: '',
    global_show: 0,     // 全局显示  0 不显示 1 显示
    playing: {
      id: '',
      name: '',
      cover: '',
      url: '',
      singer: {
        avatar: '',
        name: ''
      },
      currentTime: 0,
      duration: 0,
      timeArr: [],
      lrcArr: []
    }
  },
  audioDom: wx.getBackgroundAudioManager(),
  initAudio(){
    this.audioDom.title = '起风了';
    this.audioDom.epname = '555';
    this.audioDom.singer = 'mySkey';
    this.audioDom.coverImgUrl = 'http://img.22family.com/mySKey/favicon.ico';
    this.audioDom.paused = true;
    this.audioDom.stop = true;
  }
})

================================================
FILE: app.json
================================================
{
  "pages": [
    "pages/music/list",
    "pages/music/detail"
  ],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "WeChat",
    "navigationBarTextStyle": "black"
  },
  "requiredBackgroundModes": ["audio"]
}

================================================
FILE: app.wxss
================================================
/**app.wxss**/
@import './utils/wxss/reset.wxss';
@import './utils/wxss/icon.wxss';
@import './utils/wxss/style.wxss';
.container {
  height: 100%;
  box-sizing: border-box;
}

================================================
FILE: components/controls.js
================================================
// components/controls.js
const app = getApp()
import props from '../utils/js/props.js'
import common from '../utils/js/common.js'
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    currentTime: {
      type: Number,
      value: 0,
      observer: function (newVal, oldVal, changedPath) {
        props.setPlaying.call(this, { currentTime: newVal, duration: app.audioDom.duration })
        //props.setPlayer.call(this, { status: app.player.status })
      }
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    player:{},
  },
  attached(){
    props.setPlayer.call(this, app.player)
  },
  /**
   * 组件的方法列表
   */
  methods: {
    pauseAudio() {
      app.audioDom.pause()
      props.setPlayer.call(this, { status: 2 })
    },
    palyAudio() {
      app.audioDom.play()
      props.setPlayer.call(this, { status: 1 })
    },
    playLast() {
      if (this.data.player.list.length > 0) {
        this.triggerEvent('playLast')
      }
    },
    playNext() {
      if (this.data.player.list.length > 0) {
        this.triggerEvent('playNext')
      }
    },
    changeMode() {
      let mode = this.data.player.mode + 1
      if (mode > 2) {
        mode = 0
      }
      props.setPlayer.call(this, { mode })
    },
    changeRate() {
      common.toast('小程序不支持倍速功能!')
      return
      let playbackRate = this.data.player.playbackRate + 0.25
      if (playbackRate > 2) playbackRate = 0.5
      props.setPlayer(this, { playbackRate })
      global.audioDom.playbackRate = playbackRate;
    },
    progressTouch() {
      this.setState({ userChange: true })
    },
    changeProgress(radio) {
      let currentTime = Math.floor(radio * this.props.audio.duration)
      global.audioDom.currentTime = currentTime
      this.props.setCurrentTime(currentTime)
      this.setState({ userChange: false })
    }
  }
})


================================================
FILE: components/controls.json
================================================
{
  "component": true,
  "usingComponents": {
    "myProgress": "/components/progress"
  }
}

================================================
FILE: components/controls.wxml
================================================
<!--components/controls.wxml-->
<wxs module="time" src="../utils/wxs/time.wxs"></wxs>

<view class='controls'>
  <view class='time df-j-c'>
    <view>{{time.getAudioTime(player.playing.currentTime)}}</view>
    <view class='progress'>
      <myProgress currentTime="{{player.playing.currentTime}}"></myProgress>
    </view>
    <view>{{time.getAudioTime(player.playing.duration)}}</view>
  </view>

  <view class='btns df-j-c'>
    <view bindtap='changeMode' class='mode_rete df-j-c'>
      <i wx:if="{{player.mode === 0}}" class='mode iconfont icon-only'></i>
      <i wx:if="{{player.mode === 1}}" class='mode iconfont icon-next'></i>
      <i wx:if="{{player.mode === 2}}" class='mode iconfont icon-suiji'></i>
    </view>
    <view class='play_btn df-j-c'>
      <i bindtap='playLast' class='last_next iconfont icon-last-play'></i>
      <view>
        <i bindtap='pauseAudio' wx:if="{{player.status == 1}}" class='play_pause iconfont icon-pause'></i>
        <i bindtap='palyAudio' wx:else class='play_pause iconfont icon-play'></i>
      </view>
      <i bindtap='playNext' class='last_next iconfont icon-next-play'></i>
    </view>
    <view bindtap='changeRate' class='mode_rete df-j-c'>
      <view wx:if="{{player.playbackRate === 1}}">正常</view>
      <view wx:else>×{{player.playbackRate}}</view>
    </view>
  </view>
</view>


================================================
FILE: components/controls.wxss
================================================
/* components/controls.wxss */
@import '/utils/wxss/style.wxss';
@import '/utils/wxss/icon.wxss';
.controls{
  width: 100%;
  position: absolute;
  bottom: 2rem;
  left: 0;
}
.controls .time{
  color: #fff;
  font-size: 0.6rem;
  align-items: center;
}
.controls .time .progress{
  width: 10rem;
  margin: 0 16px;
}

.controls .btns{
  align-items: center;
  color: #dd5866;
}
.controls .btns .play_btn{
  align-items: center;
  margin: 0 2rem;
}
.controls .btns .last_next{
  font-size: 1.2rem;
}
.controls .btns .play_pause{
  font-size: 2.4rem;
  margin: 0 1rem;
}

.controls .btns .mode_rete{
  align-items: center;
  font-size: 0.7rem;
  color: #fff;
  min-width: 40px;
}
.controls .btns .mode_rete .mode{
  font-size: 1rem;
}

================================================
FILE: components/loadmore.js
================================================
// components/loadmore.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    loading: {
      type: Number,
      value: 0,
      observer: function (newVal, oldVal, changedPath) {
        this.setData({ _loading: newVal });
      }
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    _loading: 0
  },

  /**
   * 组件的方法列表
   */
  methods: {

  }
})

================================================
FILE: components/loadmore.json
================================================
{
  "component": true,
  "usingComponents": {}
}

================================================
FILE: components/loadmore.wxml
================================================
<!--components/loadmore.wxml-->
<view class='component-container wrap'>
  <view class='loading-dot' wx:if='{{_loading==1}}'>
    <view class='dot dot-1'></view>
    <view class='dot dot-2'></view>
    <view class='dot dot-3'></view>
  </view>
  <view class='all' wx:elif='{{_loading==2}}'>已加载全部</view>
  <view class='placeholder' wx:else> </view>
</view>


================================================
FILE: components/loadmore.wxss
================================================
/* components/loadmore.wxss */
.wrap{padding:20rpx 0 50rpx 0;}
.loading-dot{
  width:100%; height:40rpx; overflow:hidden; display:flex;
  align-items:center; justify-content:center;
}
.loading-dot .dot{
  display:inline-block; width:16rpx; height:16rpx; border-radius:50%; overflow: hidden; 
  background:#f2f2f2; margin:0 10rpx;
  -webkit-animation: bouncedelay 1s infinite ease-in-out;
  animation: bouncedelay 1s infinite ease-in-out;
  -webkit-animation-fill-mode: both;
  animation-fill-mode: both
}

.loading-dot .dot-1 {-webkit-animation-delay: -.48s; animation-delay: -.48s}
.loading-dot .dot-2 {-webkit-animation-delay: -.24s; animation-delay: -.24s}
@keyframes bouncedelay {
  0%,100%,80% {background:lightgray;}
  40% {background:gray;}
}
/* @keyframes bouncedelay {
  0%,100%,80% {transform: scale(0); -webkit-transform: scale(0)}
  40% {transform: scale(1);-webkit-transform: scale(1)}
} */

.all{font-size:24rpx;color:#e8e8e8;text-align:center;}
.placeholder{font-size:24rpx;}

================================================
FILE: components/lrc.js
================================================
// components/lrc.js
const app = getApp()
import props from '../utils/js/props.js'
import common from '../utils/js/common.js'
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    currentTime: {
      type: Number,
      value: 0,
      observer: function (newVal, oldVal, changedPath) {
        if (app.player.playing.lrcArr.length > 0){
          props.setPlaying.call(this, { currentTime: newVal })
          this.getCurrentLrc()
        }
      }
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    player:{},
    currentLrc: 0,
    currentWidth: 0,
    listTop: 0,
    useTime: 1,
    userChange: false,
    changeTo: 0
  },
  attached() {
    //console.log(app.player)
    this.setData({ player: app.player })
  },

  /**
   * 组件的方法列表
   */
  methods: {
    getCurrentLrc() {
      let player = app.player, audio = app.player.playing;
      let { currentTime } = audio
      let currentLrc = 0;
      audio.timeArr.forEach((v, k) => {
        if (currentTime > this.getSecond(v)) {
          currentLrc = k
        }
      })

      // 更新当前歌词
      if (currentLrc !== this.data.currentLrc) {
        this.setData({ currentLrc, currentWidth: 0 }, () => {
          this.getCurrentWidth((currentWidth)=>{
            if (currentLrc < audio.timeArr.length - 1) {
              let useTime = this.getSecond(audio.timeArr[currentLrc + 1]) - this.getSecond(audio.timeArr[currentLrc])
              // 根据当前的倍速来决定时间
              useTime = useTime / player.playbackRate
              if (useTime > 10) {
                this.setData({ useTime: 10 })
                return
              }
              this.setData({ useTime, currentWidth })
            }
          })
        })
        if (!this.data.userChange) {
          this.scrollLrc(currentLrc)
        }
      } else {
        this.getCurrentWidth((currentWidth)=>{
          this.setData({ currentWidth })
        })
      }
    },
    getCurrentWidth(cb){
      let query = wx.createSelectorQuery().in(this)
      query.select('.lrcItemShow').boundingClientRect(function (res) {
        //console.log(res.width)
        if(res){
          cb && cb(res.width)
        }
      }).exec()
    },
    getSecond(t) {
      let minute = Number(t.slice(0, 2))
      let second = Number(t.slice(3, 5))
      let minS = Number(t.slice(7))
      return minS > 100 ? (minute * 60 + second + minS / 1000) : (minute * 60 + second + minS / 100)
    },
    // 歌词滚动
    scrollLrc(currentLrc) {
      if (currentLrc) {
        let listTop = currentLrc * 32
        this.setData({ listTop })
      }
    },
    // 滑动歌词调整进度
    handleMove() {
      if(this.data.userChange){
        let query = wx.createSelectorQuery().in(this)
        query.select('.lrcList').boundingClientRect((res)=>{
          //console.log(res.top / 32)
          let currentLrc = (-Math.round(res.top / 32)) + 3
          let time = app.player.playing.timeArr[currentLrc]
          let currentTime = Math.ceil(common.getSecond(time))
          this.setData({ changeTo: currentTime })
        }).exec()
      }
    },
    changeUserStatus(){
      clearTimeout(this.timer)
      this.setData({ userChange: true })
      this.timer = setTimeout(() => {
        this.setData({ userChange: false })
      }, 5000)
    },
    changeToLrc() {
      this.setData({ userChange: false }, () => {
        wx.seekBackgroundAudio({
          position: this.data.changeTo,
        })
      })
    }
  }
})


================================================
FILE: components/lrc.json
================================================
{
  "component": true,
  "usingComponents": {}
}

================================================
FILE: components/lrc.wxml
================================================
<!--components/lrc.wxml-->
<wxs module="time" src="../utils/wxs/time.wxs"></wxs>

<view class='lrc_container'>
  <scroll-view 
    scroll-y
    style= "height:calc(100vh - 11rem)"
    bindtouchstart='changeUserStatus'
    scroll-top="{{listTop}}"
    bindscroll="handleMove"
  >
    <view class='lrcList df-col'>
      <block wx:for="{{player.playing.lrcArr}}" wx:for-item="v" wx:for-index="k" wx:key="k">
        <view class='lrcItem'>
          <view style="visibility: {{currentLrc === k ? 'hidden' : 'visible'}}; font-size: {{currentLrc === k ? '1rem' : '0.7rem'}}">{{v}}</view>
          <view wx:if="{{currentLrc === k}}" class='lrcItemShow'>{{v}}</view>
          <view style="width: {{currentWidth}}px; transition: all ease {{useTime}}s;" wx:if="{{currentLrc === k}}" class='lrcItemShowIng'>{{v}}</view>
        </view>
      </block>
    </view>
  </scroll-view>

  <view wx:if="{{userChange}}" class='swiper df-j-b'>
    <view>{{time.getAudioTime(changeTo)}}</view>
    <view class='line'></view>
    <view bindtap='changeToLrc' class='play'>
      <i class='iconfont icon-play'></i>
    </view>
  </view>
</view>


================================================
FILE: components/lrc.wxss
================================================
/* components/lrc.wxss */
@import '/utils/wxss/style.wxss';
@import '/utils/wxss/icon.wxss';
.lrc_container{
  position: absolute;
  top: 80px;
  left:0;
}
.lrcList{
  width: 100vw;
  text-align: center;
  align-items: center;
  padding-top: 160px;  /* 从第6句开始特殊滑动 */
  padding-bottom: 3rem;
}
.lrcList .lrcItem{
  color: #fff;
  height: 32px;
  line-height: 32px;
  position: relative;
  white-space: nowrap;
}
.lrcList .lrcItemShow{
  position: absolute;
  color: #fff;
  font-size: 1rem;
  top:0;
  left:0;
  z-index: 98;
  white-space: nowrap;
}
.lrcList .lrcItemShowIng{
  position: absolute;
  color: #dd5866;
  font-size: 1rem;
  top:0;
  left:0;
  z-index: 99;
  white-space: nowrap;
  overflow: hidden;
  width: 0;
}

.swiper{
  width: 100%;
  height: 20px;
  line-height: 20px;
  padding: 0 1rem;
  box-sizing: border-box;
  position: absolute;
  top: 166px;   /* 从第六上面出现刻度线 */
  left: 0;
  align-items: center;
  font-size: 28rpx;
  color: #db3e4b
}
.swiper .line{
  width: 70%;
  height: 1px;
  background: radial-gradient(at 50% 0, transparent, transparent, rgba(252, 231, 231, 0.7),rgba(252, 231, 231, 0.7));
}
.swiper .play .icon-play{
  font-size: 40rpx;
}

================================================
FILE: components/player.js
================================================
// components/player.js
const app = getApp()
import props from '../utils/js/props.js'
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    currentTime: {
      type: Number,
      value: 0,
      observer: function (newVal, oldVal, changedPath) {
        if (newVal>0) {
          props.setPlayer.call(this, app.player)
        }
      }
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    player:{}
  },
  attached(){

  },
  /**
   * 组件的方法列表
   */
  methods: {
    pauseAudio() {
      app.audioDom.pause()
      props.setPlayer.call(this, { status: 2 })
      this.triggerEvent('updatePlayer')
    },
    playAudio() {
      app.audioDom.play()
      props.setPlayer.call(this, { status: 1 })
      this.triggerEvent('updatePlayer')
    },
    playLast() {
      let current_music = app.player.current_music - 1
      if (current_music < 0) {
        current_music = app.player.list.length - 1
      }
      this.playCurrent(current_music)
    },
    playNext() {
      let current_music = app.player.current_music + 1
      if (current_music >= app.player.list.length) {
        current_music = 0
      }
      this.playCurrent(current_music)
    },
    playCurrent(current_music) {
      props.setPlayer.call(this, { current_music, status: 1 })
      props.setPlaying.call(this, app.player.list[current_music], ()=>{
        let audioDom = app.audioDom
        audioDom.title = app.player.playing.name
        audioDom.src = app.player.a_resource + app.player.playing.url
        audioDom.coverImgUrl = app.player.i_resource + app.player.playing.cover + '-ph'
        audioDom.play()
      }) 
      this.triggerEvent('updatePlayer')
    },
    closePlayer() {
      props.setPlayer.call(this, { global_show: 0 })
    },
    toDtail() {
      let { id } = app.player.playing
      let url = `/pages/music/detail?id=${id}`
      wx.navigateTo({
        url
      })
    }
  }
})


================================================
FILE: components/player.json
================================================
{
  "component": true,
  "usingComponents": {}
}

================================================
FILE: components/player.wxml
================================================
<!--components/player.wxml-->
<view bindtap='toDtail' wx:if="{{player.global_show && player.playing.id}}" class='player df-j-b'>
  <view class='avatar'>
    <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>
  </view>
  <view class='content df-1 df-j-b'>
    <view class='info df-col'>
      <view class='name'>{{player.playing.name}}</view>
      <view class='singer'>{{player.playing.singer.name}}</view>
    </view>
    <view class='right df-j-b'>
      <view class='controls df-j-b'>
        <i catchtap="playLast" class='last_next iconfont icon-last-play'></i>
        <view>
          <i catchtap="pauseAudio" wx:if="{{player.status == 1}}" class='play_pause iconfont icon-pause'></i>
          <i catchtap="playAudio" wx:else class='play_pause iconfont icon-play'></i>
        </view>
        <i catchtap='playNext' class='last_next iconfont icon-next-play'></i>
      </view>
      <view class='delete df'>
        <view class='line'></view>
        <i catchtap="closePlayer" class='iconfont icon-error'></i>
      </view>
    </view>
  </view>
</view>


================================================
FILE: components/player.wxss
================================================
/* components/player.wxss */
@import '/utils/wxss/style.wxss';
@import '/utils/wxss/icon.wxss';
.player{
  width: 90%;
  padding: 0.2rem 0.5rem;
  border-radius: 0.3rem;
  box-sizing: border-box;
  position: fixed;
  z-index: 9999;
  background: #999;
  bottom: 4rem;
  left:5%;
  align-items: center;
}

.avatar{
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  overflow: hidden;
  margin-right: 0.5rem;
}
.avatar_img{
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
}

.content{
  color: #fff;
  font-size: 0.7rem;
}
.content .singer{
  margin-top: 0.2rem;
  font-size: 0.6rem;
  color: #d2d2d2;
}
.content .right{
  align-items: center;
}
.content .right .controls{
  align-items: center;
}
.content .right .controls .last_next{
  font-size:1rem;
}
.content .right .controls .play_pause{
  font-size: 1.7rem;
  margin: 0 0.3rem;
}
.content .right .delete{
  align-items: center;
  margin-left: 1rem;
}
.content .right .delete .line{
  height: 1.3rem;
  width: 2px;
  background: #fff;
  margin-right: 0.5rem;
}

@keyframes rotateImg{
  from{transform: rotate(0);}
  to{transform: rotate(360deg);}
}

================================================
FILE: components/progress.js
================================================
// components/progress.js
const app = getApp()
import props from '../utils/js/props.js'
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    currentTime: {
      type: Number,
      value: 0,
      observer: function (newVal, oldVal, changedPath) {
        props.setPlaying.call(this, { currentTime: newVal, duration: app.audioDom.duration })
        if(!this.data.userChange){
          this.updateProgress()
        }
      }
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    player:{},
    left: 0,
    sliderW: 14,
    userChange: false,

    swiperWidth: 0,
    swiperLeft: 0,
  }, 
  ready() {
    this.getSwiperInfo()
  },  

  /**
   * 组件的方法列表
   */
  methods: {
    getSwiperInfo(){
      const that = this;
      let query = wx.createSelectorQuery().in(this)
      query.select('.swiper').boundingClientRect(function (res) {
        //console.log(res)
        that.setData({ swiperWidth: res.width, swiperLeft: res.left })
      }).exec()
    },
    updateProgress(){
      let { currentTime, duration } = app.player.playing;
      let left = Math.floor(this.data.swiperWidth * (currentTime / duration))
      this.setData({ left })
    },
    swiperTouch(e) {
      let swiperWidth = this.data.swiperWidth
      let startX = this.data.swiperLeft
      let endX = e.touches[0].clientX
      this.setData({ left: endX - startX })
      let position = ((endX - startX) / swiperWidth) * app.audioDom.duration
      wx.seekBackgroundAudio({
        position: Math.round(position),
      })
    },
    touchStart(e) {
      this.setData({ sliderW: 18, userChange: true })
    },
    touchMove(e) {
      let swiperWidth = this.data.swiperWidth
      let sliderWidth = this.data.sliderW
      let startX = this.data.swiperLeft
      let endX = e.touches[0].clientX
      if (endX - startX <= 0) {
        this.setData({ left: 0 })
        return
      }
      if (endX - startX >= (swiperWidth - sliderWidth)) {
        this.setData({ left: swiperWidth - sliderWidth })
        return
      }
      this.setData({ left: endX - startX })
    },
    touchEnd() {
      let swiperWidth = this.data.swiperWidth
      this.setData({ sliderW: 12, userChange: false })
      //this.props.changeProgress(this.data.left / swiperWidth)
      let position = (this.data.left / swiperWidth) * app.audioDom.duration
      wx.seekBackgroundAudio({
        position: Math.round(position),
      })
    }
  }
})


================================================
FILE: components/progress.json
================================================
{
  "component": true,
  "usingComponents": {}
}

================================================
FILE: components/progress.wxml
================================================
<!--components/progress.wxml-->
<view>
  <view bindtouchstart='swiperTouch' class='swiper'>
    <view style="width: {{left}}px;" class='line'></view>
    <view
      class='slider' 
      style="width: {{sliderW}}px; height: {{sliderW}}px; left: {{left}}px; top: {{-(sliderW / 2 -3)}}px"
      catchtouchstart='touchStart'
      bindtouchmove='touchMove'
      bindtouchend='touchEnd'
    ></view>
  </view>
</view>


================================================
FILE: components/progress.wxss
================================================
/* components/progress.wxss */
.swiper{
  width: 100%;
  height: 6px;
  background: #d2d2d2;
  margin: 20px 0;
  position: relative;
  border-radius: 6px;
}
.line{
  height: 6px;
  background: #dd5866;
  position: absolute;
  left: 0;
  top: 0;
  border-radius: 6px 0 0 6px;
}
.slider{
  border-radius: 50%;
  background: #33c9d4;
  position: absolute;
}

================================================
FILE: pages/music/detail.js
================================================
// pages/music/detail.js
import props from '../../utils/js/props.js'
import common from '../../utils/js/common.js'
import ajax from '../../utils/js/ajax.js'
const app = getApp()
let audioDom = app.audioDom;
Page({

  /**
   * 页面的初始数据
   */
  data: {
    player:{},
    id: '',
    audioDom:''
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    let id = options.id || 1
    if (id == app.player.playing.id){
      this.setData({ id }, ()=>{
        this.updateLrc()
      })
    }else{
      wx.stopBackgroundAudio()
      this.setData({ id: options.id || 1 }, () => {
        this.getDetail()
      })
    }
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    props.setPlayer.call(this, app.player)
    this.audioWatch()
  },
  updeData(current_music){
    if (current_music !== this.data.current_music) {
      props.setPlayer.call(this, { current_music })
      props.setPlaying.call(this, app.player.list[current_music], ()=>{
        this.playAudio()
        this.setData({ id: app.player.playing.id }, () => this.updateLrc())
      })
    }
  },
  getDetail() {
    ajax.get('audio/detail', { id: this.data.id }).then(res => {
      if (res.code === 0) {
        let { a_resource, i_resource } = res.data;
        let { lrc, id, url, cover, singer, name } = res.data.audio
        let { timeArr, lrcArr } = common.analysis(lrc)
        props.setPlaying.call(this, { id, timeArr, lrcArr, url, singer, name, cover })
        props.setPlayer.call(this, { i_resource, a_resource, global_show: 1, status: 1 })

        this.playAudio()
      }
    })
  },
  updateLrc() {
    ajax.get('lrc', { id: this.data.id }).then(res => {
      if (res.code === 0) {
        let lrc = res.data.lrc
        let { timeArr, lrcArr } = common.analysis(lrc)
        props.setPlaying.call(this, { timeArr, lrcArr })
      }
    })
  },
  audioWatch(){
    let audioDom = app.audioDom
    audioDom.onPlay(() => {
      props.setPlayer.call(this, { status: 1 })
      props.setPlaying.call(this, { duration: audioDom })
    })
    audioDom.onPause(()=>{
      props.setPlayer.call(this, { status: 2 })
    })
    audioDom.onTimeUpdate(() => {
      props.setPlaying.call(this, { currentTime: audioDom.currentTime })
    })
    audioDom.onEnded(() => {
      // 安卓监听不到。。。,并没有问题,是将接口使用错误了
      this.audioEnded()
    })
  },
  audioEnded(){
    let player = app.player, playing = app.player.playing;
    let mode = app.player.mode;
    switch (mode) {  // 0 单曲    1 顺序   2 随机
      case 0:
        console.log('单曲模式')
        this.playAudio()
        break;
      case 1:
        if (player.list.length > 0) {
          console.log('列表模式下一曲')
          let current_music = player.current_music + 1
          if (current_music >= player.list.length) {
            current_music = 0;
          }
          this.updeData(current_music)
          return
        }
        app.audioDom.pause()
        break;
      case 2:
        if (player.list.length > 0) {
          console.log('随机模式下一曲')
          let current_music = Math.floor(Math.random() * player.list.length)
          this.updeData(current_music)
          return
        }
        app.audioDom.pause()
        break;
      default:
        app.audioDom.pause()
    }
  },
  playAudio(){
    let audioDom = app.audioDom
    wx.setNavigationBarTitle({
      title: app.player.playing.name
    })
    audioDom.title = app.player.playing.name
    audioDom.src = app.player.a_resource + app.player.playing.url
    audioDom.coverImgUrl = app.player.i_resource + app.player.playing.cover + '-ph'
    audioDom.play()
  },
  playLast() {
    let current_music = app.player.current_music - 1
    if (current_music < 0) {
      current_music = app.player.list.length - 1
    }
    props.setPlayer.call(this, { current_music })
    this.setData({ current_music }, () => {
      let id = app.player.list[current_music].id
      this.setData({ id }, () => this.getDetail())
    })
  },
  playNext() {
    let current_music = app.player.current_music + 1
    if (current_music >= app.player.list.length) {
      current_music = 0
    }
    props.setPlayer.call(this, { current_music })
    this.setData({ current_music }, () => {
      let id = app.player.list[current_music].id
      this.setData({ id }, () => this.getDetail())
    })
  },
  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

================================================
FILE: pages/music/detail.json
================================================
{
  "usingComponents": {
    "lrc": "/components/lrc",
    "controls": "/components/controls"
  }
}

================================================
FILE: pages/music/detail.wxml
================================================
<!--pages/music/detail.wxml-->
<view wx:if="{{player.i_resource && player.playing.cover}}" style="background:url({{player.i_resource + player.playing.cover + '-ph'}}) 0 0 / cover;" class='detail'>
  <view class='detail_container'>
    <lrc currentTime="{{player.playing.currentTime}}"></lrc>
    <controls currentTime="{{player.playing.currentTime}}" bind:playLast="playLast" bind:playNext="playNext"></controls>
  </view>
</view>



================================================
FILE: pages/music/detail.wxss
================================================
/* pages/music/detail.wxss */
.detail{
  width: 100%;
  height: 100vh;
  overflow: hidden;
}
.detail_container{
  width: 100%;
  height: 100vh;
  position: fixed;
  top:0;
  left: 0;
  background: rgba(0,0,0,0.25);
}

================================================
FILE: pages/music/list.js
================================================
// pages/music/list.js
const app = getApp()
const dayjs = require('../../utils/js/dayjs.js')
import ajax from '../../utils/js/ajax.js'
import props from '../../utils/js/props.js'
Page({

  /**
   * 页面的初始数据
   */
  data: {
    player:{},
    musics:[
      { audios: [], page: {} },   // 推荐
      { audios: [], page: {} },   // 最热
      { audios: [], page: {} },   // 原创
      { audios: [], page: {} },   // 飙升
      { audios: [], page: {} },   // 最新
    ],
    types: ['推荐', '最热', '原创', '飙升', '最新'],
    currentType: 0,
    swiperHeight: 0,
    startX: 0,
    endX: 0,
    lineWidth: 0,
    lineLeft: 0,
    loadingArr: [0, 0, 0, 0, 0],
    p: 1
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    this.getList()
  }, 

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    this.updatePlayer()
  },
  updatePlayer(){
    props.setPlayer.call(this, app.player)
  },
  getList(){
    const type = this.data.currentType;
    let loadingArr = this.data.loadingArr
    loadingArr[type] = 1
    this.setData({ loadingArr })
    ajax.get('audio', { type, p: this.data.p }).then(res => {
      loadingArr[type] = 0
      this.setData({ loadingArr })
      if (res.code === 0) {
        let { audios, i_resource, a_resource, page } = res.data
        this.saveMusics({ type, page, audios }, ()=>{
          this.getSwiperHeight((swiperHeight) => this.setData({ swiperHeight }))
        })
        props.setPlayer.call(this, { i_resource, a_resource })
        props.setPlayer.call(this, { list: this.data.musics[type].audios })
      }
    })
  },
  saveMusics(data, cb){
    let { type, page, audios } = data;
    let musics = this.data.musics
    musics[type].audios = musics[type].audios.concat(audios)
    musics[type].page = page
    this.setData({ musics }, ()=>{
      cb && cb()
    })
  },
  dateFormat(t){
    return dayjs(t * 1000).format('YYYY.MM.DD')
  },
  changeAudio(e){
    let currentType = e.currentTarget.dataset.k
    this.setData({ currentType })
  },
  handleChangeIndex(e) {
    let currentType = e.detail.current
    let p = this.data.musics[currentType].page.p || 1
    props.setPlayer.call(this, { list: this.data.musics[currentType].audios })
    //console.log(app.player.list)
    this.setData({ p, currentType }, () => {
      if (this.data.musics[currentType].audios.length === 0) {
        this.getList()
      }
      this.getSwiperHeight((swiperHeight) => this.setData({ swiperHeight }))
    })
  },
  getSwiperHeight(cb) {
    var query = wx.createSelectorQuery().in(this)
    query.select('.swiperShow').boundingClientRect(function (res) {
      cb && cb(res.height)
    }).exec()
  },
  toDetail(event){
    let { e, i } = event.currentTarget.dataset
    props.setPlayer.call(this, { current_music: i })
    wx.navigateTo({
      url: `/pages/music/detail?id=${e.id}`
    })
  },
  handleTouchStart(e) {
    let startX = e.touches[0].clientX
    this.setData({ startX })
  },
  handleTouchMove(e) {
    let endX = e.touches[0].clientX
    if (endX - this.data.startX > 0) {
      this.setData({ lineLeft: (endX - this.data.startX) / 10, lineWidth: (endX - this.data.startX) / 10 })
    } else {
      this.setData({ lineWidth: (this.data.startX - endX) / 10 })
    }
  },
  handleTouchEnd(e) {
    this.setData({ lineWidth: 0, lineLeft: 0 })
  },
  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {
    
  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {
    
  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {
    let currentType = this.data.currentType
    if (this.data.loadingArr[currentType] !== 0) return
    if (this.data.musics[currentType].page.p >= this.data.musics[currentType].page.total_page) {
      let loadingArr = this.data.loadingArr
      loadingArr[currentType] = 2
      this.setData({ loadingArr })
      return
    }
    let p = this.data.musics[currentType].page.p + 1
    this.setData({ p }, ()=>{
      this.getList()
    })
  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

================================================
FILE: pages/music/list.json
================================================
{
  "usingComponents": {
    "loadmore": "/components/loadmore",
    "playerBar": "/components/player"
  }
}

================================================
FILE: pages/music/list.wxml
================================================
<!--pages/music/list.wxml-->
<wxs module="time" src="../../utils/wxs/time.wxs"></wxs>

<view>
  <view class='typeTitle'>
    <view class='typeList df-j-b'>
      <block wx:for="{{types}}" wx:for-item='v' wx:for-index='k' wx:key='k'>
        <view bindtap='changeAudio' data-k="{{k}}" class="{{k===currentType ? 'itemShow' : ''}} typeItem">{{v}}</view>
      </block>
    </view>
    <view class='navSwiper'>
      <view style="left:{{currentType * 20}}%" class='navSlider'>
        <view 
        style="width:calc(60% + {{lineWidth}}px); margin-left:calc(20% - {{lineLeft}}px); transition: {{lineWidth > 0 ? '' : 'all 0.5s'}};" 
        class='navLine'></view>
      </view>
    </view>
  </view>

  <swiper
    circular="{{true}}"
    current="{{currentType}}"
    style="width:100%;height:{{swiperHeight}}px;"
    bindchange='handleChangeIndex'
  >
    <block wx:for="{{musics}}" wx:for-item='v' wx:for-index='k' wx:key='k'>
      <swiper-item>
        <view bindtouchstart='handleTouchStart' bindtouchmove='handleTouchMove' bindtouchend='handleTouchEnd' class="{{currentType === k ? 'swiperShow' : 'swiper'}}">
          <block wx:for="{{v.audios}}" wx:for-item="e" wx:for-index="i" wx:key="i">
            <view bindtap='toDetail' data-e="{{e}}" data-i="{{i}}" class="item df {{player.playing.id === e.id && player.status === 1? 'item_show' : ''}}">
              <image wx:if="{{player.i_resource}}" class='item_cover' src="{{player.i_resource + e.cover + '-cover'}}"></image>
              <view class='item_info df-1'>
                <view class='item_name'>{{e.name}}</view>
                <view class='singer_name df'>
                  <view class='singer_avatar'>
                    <image wx:if="{{player.i_resource}}" class='avatar_img' src="{{player.i_resource + e.singer.avatar + '-avatar'}}"></image>
                  </view>
                  {{e.singer.name}}
                </view>
                <view class='item_date'>{{time.dateFormat(e.date)}}</view>
              </view>
            </view>
          </block>
        </view>
      </swiper-item>
    </block>
  </swiper>

  <loadmore class="component-container" loading='{{loadingArr[currentType]}}'></loadmore>
  <playerBar currentTime="{{player.playing.currentTime}}" bind:updatePlayer="updatePlayer"></playerBar>
</view>


================================================
FILE: pages/music/list.wxss
================================================
/* pages/music/list.wxss */
.typeTitle{
  width: 100%;
  position: fixed;
  left: 0;
  top: 0;
  z-index: 999;
  background: #fff;
  border-bottom: 1px solid #eee;
}
.typeList{
  height: 1.8rem;
  padding-bottom: 0.4rem;
  font-size: 0.7rem;
  color: #333;
  align-items: flex-end;
}
.typeItem{
  width: 20%;
  transition: all 0.3s;
  text-align: center;
}
.itemShow{
  font-size: 0.9rem;
  color: #33c9d4;
}

.navSwiper{
  position: relative;
}
.navSlider{
  width: 20%;
  height: 2px;
  position: absolute;
  bottom: -1px;
  transition: all 0.5s;
}
.navLine{
  height: 100%;
  background: #33c9d4;
  width: 60%;
  margin-left: 20%;
}


.swiper{
  width: 100%;
  padding-top: 2rem;
  padding-bottom: 3rem;
}
.swiperShow{
  width: 100%;
  padding-top: 2rem;
  padding-bottom: 3rem;
  max-height: auto;
}

.item{
  color: #333;
  padding: 1rem 0.75rem 0.5rem 0.75rem;
  border-top: 1px solid #eee;
  align-items: center;
}
.swiper .item:first-child, .swiperShow .item:first-child{
  border: none;
}
.item .item_cover{
  width: 4rem;
  height: 4rem;
  border-radius: 50%;
}
.item_show{
  color: #fff;
  background: url('http://audio.22family.com//gif/playing.gif') 0 0 /cover;
}
.item .item_info{
  width: 100%;
  min-height: 4rem;
  padding: 0.5rem 0;
  padding-left: 1rem;
}
.item .item_info .item_name{
  font-size: 0.8rem;
  align-self: flex-start;
}
.item .item_info .singer_name{
  align-items: center;
  padding: 0.7rem 0 0.3rem 0;
  font-size: 0.7rem;
  color: #666;
}
.item .item_info .singer_name .singer_avatar{
  width: 1.4rem;
  display: flex;
  justify-content: center;
  align-items: center;
  margin-right: 0.4rem;
}
.item .item_info .singer_name .avatar_img{
  width: 1rem;
  height:1rem;
  border-radius: 50%;
}
.item .item_info .item_date{
  font-size: 0.6rem;
  color: #999;
}

================================================
FILE: project.config.json
================================================
{
	"description": "项目配置文件",
	"packOptions": {
		"ignore": []
	},
	"setting": {
		"urlCheck": true,
		"es6": true,
		"postcss": true,
		"minified": true,
		"newFeature": true,
		"autoAudits": false
	},
	"compileType": "miniprogram",
	"libVersion": "2.6.2",
	"appid": "wx7d3aad6d394ff0ab",
	"projectname": "mySkey",
	"debugOptions": {
		"hidedInDevtools": []
	},
	"isGameTourist": false,
	"condition": {
		"search": {
			"current": -1,
			"list": []
		},
		"conversation": {
			"current": -1,
			"list": []
		},
		"plugin": {
			"current": -1,
			"list": []
		},
		"game": {
			"currentL": -1,
			"list": []
		},
		"miniprogram": {
			"current": 0,
			"list": [
				{
					"id": -1,
					"name": "播放页",
					"pathName": "pages/music/detail",
					"query": "id=3",
					"scene": null
				}
			]
		}
	}
}

================================================
FILE: utils/js/ajax.js
================================================
let api_url = 'https://www.22family.com/music/test/'
export default{
  get(url, data){
    return new Promise((resolve, reject)=>{
      wx.request({
        url: api_url + url,
        method: 'GET',
        data,
        header: {
          'content-type': 'application/json' // 默认值
        },
        success(res) {
          resolve(res.data)
        },
        error(err){
          reject(err)
        }
      })
    })
  },
  post(url, data) {
    return new Promise((resolve, reject) => {
      wx.request({
        url: api_url + url,
        method: 'POST',
        data,
        header: {
          'content-type': 'application/json' // 默认值
        },
        success(res) {
          resolve(res.data)
        },
        error(err) {
          reject(err)
        }
      })
    })
  }
}

================================================
FILE: utils/js/audio.js
================================================


================================================
FILE: utils/js/common.js
================================================
export default {
  toast(title, duration = 1500){
    wx.showToast({
      title,
      icon: 'none',
      duration
    })
  },
  confirm(content, cb){
    wx.showModal({
      title: '温馨提示',
      content,
      success(res) {
        if (res.confirm) {
          cb && cb()
        }
      }
    })
  },
  showLoading(title){
    wx.showLoading({
      title
    })
  },
  hideLoading(){
    wx.hideLoading()
  },
  analysis(str) {
    str = str.slice(str.indexOf('00:00.00'))
    let s = str.replace(/[\s\r\n]/g, "").split('[');
    let timeArr = [], lrcArr = [];
    for (let v of s) {
      let lrc = v.split(']')
      timeArr.push(lrc[0])
      lrcArr.push(lrc[1])
    }
    return { timeArr, lrcArr }
  },
  getAudioTime(num = 0) {
    let minute = Math.floor(num / 60).toString();
    let second = Math.floor(num % 60).toString();
    return `${minute.padStart(2, '0')} : ${second.padStart(2, '0')}`
  },
  getSecond(t) {
    let minute = Number(t.slice(0, 2))
    let second = Number(t.slice(3, 5))
    let minS = Number(t.slice(7))
    return minS > 100 ? (minute * 60 + second + minS / 1000) : (minute * 60 + second + minS / 100)
  },
}

================================================
FILE: utils/js/dayjs.js
================================================
!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 });

================================================
FILE: utils/js/props.js
================================================
const app = getApp()
export default class Props{
  static setPlayer(player, cb=null){
    app.player = Object.assign({}, app.player, player)
    this.setData({ player: app.player }, ()=>{
      cb && cb()
    })
  }
  static setPlaying(playing, cb=null){
    app.player.playing = Object.assign({}, app.player.playing, playing)
    this.setData({ player: app.player }, ()=>{
      cb && cb()
    })
  }
}

================================================
FILE: utils/wxs/time.wxs
================================================
// wxs中只支持到es4的语法,所以别使用es6
module.exports = {
  getAudioTime: function(num) {
    num = Number(num)
    var minute = Math.floor(num / 60)
    var second = Math.floor(num % 60)
    return ( minute>9 ? minute : '0'+minute) + ' : ' + (second>9 ? second : '0'+second)
  },
  dateFormat: function(t) {
    var date = getDate(t * 1000)
    var year = date.getFullYear()
    var month = date.getMonth() + 1
    var day = date.getDate()
    return year + '.' + (month>9 ? month : '0' + month) + '.' + (day>9 ? day : '0' + day)
  }
}

================================================
FILE: utils/wxss/icon.wxss
================================================
@font-face {font-family: "iconfont";
  src: url('//at.alicdn.com/t/font_1101476_zl0zw7wq9p8.eot?t=1553943371044'); /* IE9 */
  src: url('//at.alicdn.com/t/font_1101476_zl0zw7wq9p8.eot?t=1553943371044#iefix') format('embedded-opentype'), /* IE6-IE8 */
  url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
  url('//at.alicdn.com/t/font_1101476_zl0zw7wq9p8.woff?t=1553943371044') format('woff'),
  url('//at.alicdn.com/t/font_1101476_zl0zw7wq9p8.ttf?t=1553943371044') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
  url('//at.alicdn.com/t/font_1101476_zl0zw7wq9p8.svg?t=1553943371044#iconfont') format('svg'); /* iOS 4.1- */
}

.iconfont {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-news:before {
  content: "\e63d";
}

.icon-last-play:before {
  content: "\e60b";
}

.icon-next-play:before {
  content: "\e611";
}

.icon-music:before {
  content: "\e794";
}

.icon-menu:before {
  content: "\e62e";
}

.icon-only:before {
  content: "\e607";
}

.icon-error:before {
  content: "\e766";
}

.icon-loading:before {
  content: "\e65d";
}

.icon-suiji:before {
  content: "\e802";
}

.icon-next:before {
  content: "\e60f";
}

.icon-fm:before {
  content: "\e65b";
}

.icon-pause:before {
  content: "\e620";
}

.icon-play:before {
  content: "\e621";
}


================================================
FILE: utils/wxss/reset.wxss
================================================
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
  display: block;
}
body {
  line-height: 1;
}
ol, ul {
  list-style: none;
}
blockquote, q {
  quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
  content: '';
  content: none;
}
table {
  border-collapse: collapse;
  border-spacing: 0;
}


================================================
FILE: utils/wxss/style.wxss
================================================
html{
  font-size: 7.3333333333vw;
  background: #fff;
  -moz-user-select: none; /*火狐*/
  -webkit-user-select: none;  /*webkit浏览器*/
  -ms-user-select: none;   /*IE10*/
  -khtml-user-select: none; /*早期浏览器*/
  user-select: none;
  margin: 0;
  padding: 0;
}
body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
}
view{
  margin: 0;
  padding: 0;
}
ul>li{
  list-style: none;
}
.df{
  display: flex;
}
.df-1{
  flex: 1;
}
.df-col{
  display: flex;
  flex-direction: column;
}
.df-j-b{
  display: flex;
  justify-content: space-between;
}
.df-j-c{
  display: flex;
  justify-content: center;
}
Download .txt
gitextract_ec2v8ip_/

├── README.md
├── app.js
├── app.json
├── app.wxss
├── components/
│   ├── controls.js
│   ├── controls.json
│   ├── controls.wxml
│   ├── controls.wxss
│   ├── loadmore.js
│   ├── loadmore.json
│   ├── loadmore.wxml
│   ├── loadmore.wxss
│   ├── lrc.js
│   ├── lrc.json
│   ├── lrc.wxml
│   ├── lrc.wxss
│   ├── player.js
│   ├── player.json
│   ├── player.wxml
│   ├── player.wxss
│   ├── progress.js
│   ├── progress.json
│   ├── progress.wxml
│   └── progress.wxss
├── pages/
│   └── music/
│       ├── detail.js
│       ├── detail.json
│       ├── detail.wxml
│       ├── detail.wxss
│       ├── list.js
│       ├── list.json
│       ├── list.wxml
│       └── list.wxss
├── project.config.json
└── utils/
    ├── js/
    │   ├── ajax.js
    │   ├── audio.js
    │   ├── common.js
    │   ├── dayjs.js
    │   └── props.js
    ├── wxs/
    │   └── time.wxs
    └── wxss/
        ├── icon.wxss
        ├── reset.wxss
        └── style.wxss
Download .txt
SYMBOL INDEX (66 symbols across 11 files)

FILE: app.js
  method onShow (line 9) | onShow(){
  method initAudio (line 42) | initAudio(){

FILE: components/controls.js
  method attached (line 26) | attached(){
  method pauseAudio (line 33) | pauseAudio() {
  method palyAudio (line 37) | palyAudio() {
  method playLast (line 41) | playLast() {
  method playNext (line 46) | playNext() {
  method changeMode (line 51) | changeMode() {
  method changeRate (line 58) | changeRate() {
  method progressTouch (line 66) | progressTouch() {
  method changeProgress (line 69) | changeProgress(radio) {

FILE: components/lrc.js
  method attached (line 34) | attached() {
  method getCurrentLrc (line 43) | getCurrentLrc() {
  method getCurrentWidth (line 78) | getCurrentWidth(cb){
  method getSecond (line 87) | getSecond(t) {
  method scrollLrc (line 94) | scrollLrc(currentLrc) {
  method handleMove (line 101) | handleMove() {
  method changeUserStatus (line 113) | changeUserStatus(){
  method changeToLrc (line 120) | changeToLrc() {

FILE: components/player.js
  method attached (line 26) | attached(){
  method pauseAudio (line 33) | pauseAudio() {
  method playAudio (line 38) | playAudio() {
  method playLast (line 43) | playLast() {
  method playNext (line 50) | playNext() {
  method playCurrent (line 57) | playCurrent(current_music) {
  method closePlayer (line 68) | closePlayer() {
  method toDtail (line 71) | toDtail() {

FILE: components/progress.js
  method ready (line 33) | ready() {
  method getSwiperInfo (line 41) | getSwiperInfo(){
  method updateProgress (line 49) | updateProgress(){
  method swiperTouch (line 54) | swiperTouch(e) {
  method touchStart (line 64) | touchStart(e) {
  method touchMove (line 67) | touchMove(e) {
  method touchEnd (line 82) | touchEnd() {

FILE: pages/music/detail.js
  method updeData (line 49) | updeData(current_music){
  method getDetail (line 58) | getDetail() {
  method updateLrc (line 71) | updateLrc() {
  method audioWatch (line 80) | audioWatch(){
  method audioEnded (line 97) | audioEnded(){
  method playAudio (line 130) | playAudio(){
  method playLast (line 140) | playLast() {
  method playNext (line 151) | playNext() {

FILE: pages/music/list.js
  method updatePlayer (line 51) | updatePlayer(){
  method getList (line 54) | getList(){
  method saveMusics (line 72) | saveMusics(data, cb){
  method dateFormat (line 81) | dateFormat(t){
  method changeAudio (line 84) | changeAudio(e){
  method handleChangeIndex (line 88) | handleChangeIndex(e) {
  method getSwiperHeight (line 100) | getSwiperHeight(cb) {
  method toDetail (line 106) | toDetail(event){
  method handleTouchStart (line 113) | handleTouchStart(e) {
  method handleTouchMove (line 117) | handleTouchMove(e) {
  method handleTouchEnd (line 125) | handleTouchEnd(e) {

FILE: utils/js/ajax.js
  method get (line 3) | get(url, data){
  method post (line 21) | post(url, data) {

FILE: utils/js/common.js
  method toast (line 2) | toast(title, duration = 1500){
  method confirm (line 9) | confirm(content, cb){
  method showLoading (line 20) | showLoading(title){
  method hideLoading (line 25) | hideLoading(){
  method analysis (line 28) | analysis(str) {
  method getAudioTime (line 39) | getAudioTime(num = 0) {
  method getSecond (line 44) | getSecond(t) {

FILE: utils/js/dayjs.js
  function h (line 1) | function h(t) { this.parse(t) }

FILE: utils/js/props.js
  class Props (line 2) | class Props{
    method setPlayer (line 3) | static setPlayer(player, cb=null){
    method setPlaying (line 9) | static setPlaying(playing, cb=null){
Condensed preview — 42 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (59K chars).
[
  {
    "path": "README.md",
    "chars": 460,
    "preview": "### 扫码预览\n\n<div align=center><img src=\"http://img.22family.com/mySKey/small.jpg\" /></div>\n\n### 一、需求分析\n\n* 基本功能\n\n上一曲、下一曲、播放"
  },
  {
    "path": "app.js",
    "chars": 1047,
    "preview": "//app.js\nApp({\n  onLaunch: function () {\n    wx.setNavigationBarTitle({\n      title: 'mySkey音乐'\n    })\n    this.initAudi"
  },
  {
    "path": "app.json",
    "chars": 286,
    "preview": "{\n  \"pages\": [\n    \"pages/music/list\",\n    \"pages/music/detail\"\n  ],\n  \"window\": {\n    \"backgroundTextStyle\": \"light\",\n "
  },
  {
    "path": "app.wxss",
    "chars": 175,
    "preview": "/**app.wxss**/\n@import './utils/wxss/reset.wxss';\n@import './utils/wxss/icon.wxss';\n@import './utils/wxss/style.wxss';\n."
  },
  {
    "path": "components/controls.js",
    "chars": 1821,
    "preview": "// components/controls.js\nconst app = getApp()\nimport props from '../utils/js/props.js'\nimport common from '../utils/js/"
  },
  {
    "path": "components/controls.json",
    "chars": 92,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {\n    \"myProgress\": \"/components/progress\"\n  }\n}"
  },
  {
    "path": "components/controls.wxml",
    "chars": 1338,
    "preview": "<!--components/controls.wxml-->\n<wxs module=\"time\" src=\"../utils/wxs/time.wxs\"></wxs>\n\n<view class='controls'>\n  <view c"
  },
  {
    "path": "components/controls.wxss",
    "chars": 731,
    "preview": "/* components/controls.wxss */\n@import '/utils/wxss/style.wxss';\n@import '/utils/wxss/icon.wxss';\n.controls{\n  width: 10"
  },
  {
    "path": "components/loadmore.js",
    "chars": 353,
    "preview": "// components/loadmore.js\nComponent({\n  /**\n   * 组件的属性列表\n   */\n  properties: {\n    loading: {\n      type: Number,\n      "
  },
  {
    "path": "components/loadmore.json",
    "chars": 48,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "components/loadmore.wxml",
    "chars": 355,
    "preview": "<!--components/loadmore.wxml-->\n<view class='component-container wrap'>\n  <view class='loading-dot' wx:if='{{_loading==1"
  },
  {
    "path": "components/loadmore.wxss",
    "chars": 990,
    "preview": "/* components/loadmore.wxss */\n.wrap{padding:20rpx 0 50rpx 0;}\n.loading-dot{\n  width:100%; height:40rpx; overflow:hidden"
  },
  {
    "path": "components/lrc.js",
    "chars": 3411,
    "preview": "// components/lrc.js\nconst app = getApp()\nimport props from '../utils/js/props.js'\nimport common from '../utils/js/commo"
  },
  {
    "path": "components/lrc.json",
    "chars": 48,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "components/lrc.wxml",
    "chars": 1124,
    "preview": "<!--components/lrc.wxml-->\n<wxs module=\"time\" src=\"../utils/wxs/time.wxs\"></wxs>\n\n<view class='lrc_container'>\n  <scroll"
  },
  {
    "path": "components/lrc.wxss",
    "chars": 1171,
    "preview": "/* components/lrc.wxss */\n@import '/utils/wxss/style.wxss';\n@import '/utils/wxss/icon.wxss';\n.lrc_container{\n  position:"
  },
  {
    "path": "components/player.js",
    "chars": 1886,
    "preview": "// components/player.js\nconst app = getApp()\nimport props from '../utils/js/props.js'\nComponent({\n  /**\n   * 组件的属性列表\n   "
  },
  {
    "path": "components/player.json",
    "chars": 48,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "components/player.wxml",
    "chars": 1241,
    "preview": "<!--components/player.wxml-->\n<view bindtap='toDtail' wx:if=\"{{player.global_show && player.playing.id}}\" class='player "
  },
  {
    "path": "components/player.wxss",
    "chars": 1112,
    "preview": "/* components/player.wxss */\n@import '/utils/wxss/style.wxss';\n@import '/utils/wxss/icon.wxss';\n.player{\n  width: 90%;\n "
  },
  {
    "path": "components/progress.js",
    "chars": 2404,
    "preview": "// components/progress.js\nconst app = getApp()\nimport props from '../utils/js/props.js'\nComponent({\n  /**\n   * 组件的属性列表\n "
  },
  {
    "path": "components/progress.json",
    "chars": 48,
    "preview": "{\n  \"component\": true,\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "components/progress.wxml",
    "chars": 416,
    "preview": "<!--components/progress.wxml-->\n<view>\n  <view bindtouchstart='swiperTouch' class='swiper'>\n    <view style=\"width: {{le"
  },
  {
    "path": "components/progress.wxss",
    "chars": 354,
    "preview": "/* components/progress.wxss */\n.swiper{\n  width: 100%;\n  height: 6px;\n  background: #d2d2d2;\n  margin: 20px 0;\n  positio"
  },
  {
    "path": "pages/music/detail.js",
    "chars": 4715,
    "preview": "// pages/music/detail.js\nimport props from '../../utils/js/props.js'\nimport common from '../../utils/js/common.js'\nimpor"
  },
  {
    "path": "pages/music/detail.json",
    "chars": 99,
    "preview": "{\n  \"usingComponents\": {\n    \"lrc\": \"/components/lrc\",\n    \"controls\": \"/components/controls\"\n  }\n}"
  },
  {
    "path": "pages/music/detail.wxml",
    "chars": 432,
    "preview": "<!--pages/music/detail.wxml-->\n<view wx:if=\"{{player.i_resource && player.playing.cover}}\" style=\"background:url({{playe"
  },
  {
    "path": "pages/music/detail.wxss",
    "chars": 216,
    "preview": "/* pages/music/detail.wxss */\n.detail{\n  width: 100%;\n  height: 100vh;\n  overflow: hidden;\n}\n.detail_container{\n  width:"
  },
  {
    "path": "pages/music/list.js",
    "chars": 4163,
    "preview": "// pages/music/list.js\nconst app = getApp()\nconst dayjs = require('../../utils/js/dayjs.js')\nimport ajax from '../../uti"
  },
  {
    "path": "pages/music/list.json",
    "chars": 108,
    "preview": "{\n  \"usingComponents\": {\n    \"loadmore\": \"/components/loadmore\",\n    \"playerBar\": \"/components/player\"\n  }\n}"
  },
  {
    "path": "pages/music/list.wxml",
    "chars": 2308,
    "preview": "<!--pages/music/list.wxml-->\n<wxs module=\"time\" src=\"../../utils/wxs/time.wxs\"></wxs>\n\n<view>\n  <view class='typeTitle'>"
  },
  {
    "path": "pages/music/list.wxss",
    "chars": 1794,
    "preview": "/* pages/music/list.wxss */\n.typeTitle{\n  width: 100%;\n  position: fixed;\n  left: 0;\n  top: 0;\n  z-index: 999;\n  backgro"
  },
  {
    "path": "project.config.json",
    "chars": 800,
    "preview": "{\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\"pos"
  },
  {
    "path": "utils/js/ajax.js",
    "chars": 799,
    "preview": "let api_url = 'https://www.22family.com/music/test/'\nexport default{\n  get(url, data){\n    return new Promise((resolve, "
  },
  {
    "path": "utils/js/audio.js",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "utils/js/common.js",
    "chars": 1149,
    "preview": "export default {\n  toast(title, duration = 1500){\n    wx.showToast({\n      title,\n      icon: 'none',\n      duration\n   "
  },
  {
    "path": "utils/js/dayjs.js",
    "chars": 7975,
    "preview": "!function (t, e) { \"object\" == typeof exports && \"undefined\" != typeof module ? module.exports = e() : \"function\" == typ"
  },
  {
    "path": "utils/js/props.js",
    "chars": 403,
    "preview": "const app = getApp()\nexport default class Props{\n  static setPlayer(player, cb=null){\n    app.player = Object.assign({},"
  },
  {
    "path": "utils/wxs/time.wxs",
    "chars": 524,
    "preview": "// wxs中只支持到es4的语法,所以别使用es6\nmodule.exports = {\n  getAudioTime: function(num) {\n    num = Number(num)\n    var minute = Mat"
  },
  {
    "path": "utils/wxss/icon.wxss",
    "chars": 4872,
    "preview": "@font-face {font-family: \"iconfont\";\n  src: url('//at.alicdn.com/t/font_1101476_zl0zw7wq9p8.eot?t=1553943371044'); /* IE"
  },
  {
    "path": "utils/wxss/reset.wxss",
    "chars": 1005,
    "preview": "html, body, div, span, applet, object, iframe,\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\na, abbr, acronym, address, bi"
  },
  {
    "path": "utils/wxss/style.wxss",
    "chars": 597,
    "preview": "html{\n  font-size: 7.3333333333vw;\n  background: #fff;\n  -moz-user-select: none; /*火狐*/\n  -webkit-user-select: none;  /*"
  }
]

About this extraction

This page contains the full source code of the mySkey/music-small GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 42 files (51.7 KB), approximately 18.8k tokens, and a symbol index with 66 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!