微信小程序音樂播放器,leancloud後端支持,體驗小程序數據綁定,Promise較爲優雅解決回調


初窺



todo:css

  • [ ] 添加音樂到收藏(最近)列表html

  • [ ] 歌詞滾動前端

從一個hello world開始

微信開發者工具生成 目錄以下:html5

.
|-- app.js
|-- app.json
|-- app.wxss
|-- pages     
|   |-- index   # 主頁
|   |   |-- index.js
|   |   |-- index.json
|   |   |-- index.wxml
|   |   `-- index.wxss
|   `-- log # 日誌頁面
|   |   |-- log.js
|   |   |-- log.json
|   |   |-- log.wxml
|   |   `-- log.wxss
`-- utils       # 工具
    `-- util.js

大致爲:
每個page便是一個頁面文件 ,每一個頁面有一個js/wxml/wxss/json文件 規定:描述頁面的這四個文件必須具備相同的路徑與文件名。

全局下同路,爲公共的邏輯,樣式,配置

與html不一樣:用view text navigator 代替 div span anode

開發者文檔蜻蜓點水

app.json: 註冊pages window tabBar networkTimeout

組件說明

*.js: 做爲邏輯層 與wxml交互 有着豐富的
網絡,
媒體,
文件,
數據緩存,
位置,
設備,
界面...的api

官方文檔

*.wxml: 數據驅動的視圖層 + 微信提供了大量的組件 表單 導航 媒體 ...css3

官方組件不夠,weui來湊

weui爲小程序提供了 weui.wxcss 但大可能是造官方組件的輪子

這裏精選,也算是補充兩個經常使用組件

對於小程序沒有DOM操做 不熟悉mvvm思想的同窗 是個很好的入門git

  1. navbar

    navbares6

<!-- wxml -->
<view class="weui-tab">
            <view class="weui-navbar">
                <block wx:for="{{tabs}}" wx:key="*this">
                    <view id="{{index}}" class="weui-navbar__item {{activeIndex == index ? 'weui-bar__item_on' : ''}}" bindtap="tabClick">
                        <view class="weui-navbar__title">{{item}}</view>
                    </view>
                </block>
                <view class="weui-navbar__slider" style="left: {{sliderLeft}}px; transform: translateX({{sliderOffset}}px); -webkit-transform: translateX({{sliderOffset}}px);"></view>
            </view>
            <view class="weui-tab__panel">
                <view class="weui-tab__content" hidden="{{activeIndex != 0}}">選項一的內容</view>
                <view class="weui-tab__content" hidden="{{activeIndex != 1}}">選項二的內容</view>
                <view class="weui-tab__content" hidden="{{activeIndex != 2}}">選項三的內容</view>
            </view>
 </view>

block渲染data裏面的四個tabs,slider爲激活tab選項時候的表現,panel爲內容面板github

//js
var sliderWidth = 96; // 須要設置slider的寬度,用於計算中間位置
Page({
    data: {
        tabs: ["選項一", "選項二", "選項三"],
        activeIndex: 1,
        sliderOffset: 0,
        sliderLeft: 0
    },
    onLoad: function () {
        var that = this;
        wx.getSystemInfo({
            success: function(res) {
                that.setData({
                    sliderLeft: (res.windowWidth / that.data.tabs.length - sliderWidth) / 2,
                    sliderOffset: res.windowWidth / that.data.tabs.length * that.data.activeIndex
                });
            }
        });
    },
    tabClick: function (e) {
        this.setData({
            sliderOffset: e.currentTarget.offsetLeft,
            activeIndex: e.currentTarget.id
        });
    }
});

瞭解mvvm思想的同窗不難看出 經過tabs數組渲染出來選項後每次點擊獲取id 而後經過設置hidden顯示或隱藏web

  1. searchbar

    searchbar

<view class="weui-search-bar">
            <view class="weui-search-bar__form">
                <view class="weui-search-bar__box">
                    <icon class="weui-icon-search_in-box" type="search" size="14"></icon>
                    <input type="text" class="weui-search-bar__input" placeholder="搜索" value="{{inputVal}}" focus="{{inputShowed}}" bindinput="inputTyping" />
                    <view class="weui-icon-clear" wx:if="{{inputVal.length > 0}}" bindtap="clearInput">
                        <icon type="clear" size="14"></icon>
                    </view>
                </view>
                <label class="weui-search-bar__label" hidden="{{inputShowed}}" bindtap="showInput">
                    <icon class="weui-icon-search" type="search" size="14"></icon>
                    <view class="weui-search-bar__text">搜索</view>
                </label>
            </view>
            <view class="weui-search-bar__cancel-btn" hidden="{{!inputShowed}}" bindtap="hideInput">取消</view>
        </view>
        <view class="weui-cells searchbar-result" wx:if="{{inputVal.length > 0}}">
            <navigator url="" class="weui-cell" hover-class="weui-cell_active">
                <view class="weui-cell__bd">
                    <view>實時搜索文本</view>
                </view>
            </navigator>
        </view>

一個input輸入框+一個搜索label+一個清楚內容的icon + 取消按鈕

Page({
    data: {
        inputShowed: false,
        inputVal: ""
    },
    showInput: function () {
        this.setData({
            inputShowed: true
        });
    },
    hideInput: function () {
        this.setData({
            inputVal: "",
            inputShowed: false
        });
    },
    clearInput: function () {
        this.setData({
            inputVal: ""
        });
    },
    inputTyping: function (e) {
        this.setData({
            inputVal: e.detail.value
        });
    }
});

input上面有一層label 經過Page裏面狀態的改變而操做其wxml狀態的改變

不難體會到:小程序和Vue的思想仍是挺接近的

站在巨人的肩膀上--雲音樂api

---獲取雲音樂api

巨人的源github項目

在此我將他部署到leancloud上

便可在線訪問,免去煩人的本地localhost啓動,在線url

http://neteasemusic.leanapp.cn

調用例子:

http://neteasemusic.leanapp.c...海闊天空

http://neteasemusic.leanapp.c...

搜索結果
具體參考api

詳細文檔

一切具有 只欠東風

生成目錄
本文講解核心內容音樂的播放,讀者可本身實現其他頁面。

.
|-- app.js
|-- app.json
|-- app.wxss
|-- common.js #公用js
|-- images #存放項目圖片
|-- style
|   |-- weui.wxss   # 引入weui樣式  萬一你本身不想寫css樣式呢
|-- pages
|   |-- find   # 發現音樂
|   |   |-- index.js
|   |   |-- index.json
|   |   |-- index.wxml
|   |   `-- index.wxss
|   |--my   # 個人音樂
|   |   |-- index.js
|   |   |-- index.json
|   |   |-- index.wxml
|   |   `-- index.wxss
|   |--now  # 正在播放
|   |   |-- index.js
|   |   |-- index.json
|   |   |-- index.wxml
|   |   `-- index.wxss
|   |--account   # 帳號
|   |   |-- index.js
|   |   |-- index.json
|   |   |-- index.wxml
|   |   `-- index.wxss
|   |-- index   # 主頁
|   |   |-- index.js
|   |   |-- index.json
|   |   |-- index.wxml
|   |   `-- index.wxss
|   `-- log # 日誌頁面
`-- utils       # 工具
    `-- util.js

請先在在app.json中註冊頁面,設置navigation,配置tabbar

{
  "pages":[
    "pages/find/index",
    "pages/my/index",
    "pages/now/index",
    "pages/account/index",
    "pages/index/index"
  ],
  "window":{
    "backgroundTextStyle":"light",
    "navigationBarBackgroundColor": "#D43C33",
    "navigationBarTitleText": "網易雲音樂",
    "navigationBarTextStyle":"white",
    "backgroundColor": "#FBFCFD"
  },
  "tabBar": {
    "backgroundColor":"#2A2C2E",
    "color": "#a7a7a7",
     "selectedColor": "#ffffff",
    "list": [{
      "iconPath":"./images/find.png",
      "selectedIconPath":"./images/find1.png",
      "pagePath":"pages/find/index",
      "text": "發現音樂"
    }, {
      "iconPath":"./images/my.png",
      "selectedIconPath":"./images/my1.png",
      "pagePath": "pages/my/index",
      "text": "個人音樂"
    }, {
      "iconPath":"./images/now.png",
      "selectedIconPath":"./images/now1.png",
      "pagePath": "pages/now/index",
      "text": "正在播放"
    }, {
      "iconPath":"./images/account.png",
      "selectedIconPath":"./images/account1.png",
      "pagePath": "pages/account/index",
      "text": "帳號"
    }]
  }
}
  • 發現音樂


佈局分爲搜索框,navbar,swiper滑動,三列,以及兩行三列構成

tips:小程序中flex佈局基本無兼容性問題 ,可大膽使用

前三個可用上文提到的組件和小程序swiper組件快速完成,

對於搜索功能

咱們在搜索input上綁定一個inputTyping事件,這樣每次鍵入完畢均可以獲得結果,而後咱們直接請求api

//index.js
//獲取應用實例
// 我的網易雲音樂 ID  66919655
var app = getApp()
Page({
    data: {
        searchReault: []
    },
    //綁定事件
    inputTyping: function (e) {
        let that = this
        console.log(e.detail)
        this.setData({
            inputVal: e.detail.value
        });
        wx.request({
            url: 'http://neteasemusic.leanapp.cn/search',
            data: {
                keywords: e.detail.value
            },
            method: 'GET',
            success: function (res) {
                let temp = []
                if(!res.data.result.songs){
                    return ;
                }
                //遍歷數據
                res.data.result.songs.forEach((song, index) => {
                    temp.push({
                        id: song.id,
                        name: song.name,
                        mp3Url: song.mp3Url,
                        picUrl: song.album.picUrl,
                        singer: song.artists[0].name
                    })
                    //設置數據
                   that.setData({
                        searchReault: temp
                    })
                })
                // 存入搜索的結果進緩存
                wx.setStorage({
                    key:"searchReault",
                    data:temp
                })
            }
        })
    }
});

data裏面的searchReault數組存入搜索結果,發起一個wx.request,用GET方式傳入參數,組織好json後設置data,而後將搜索結果存入本地緩存

wxml渲染searchReault:



而且自定義data屬性,navigator的打開方式爲tab切換open-type="switchTab" ,綁定一個tonow事件bindtap="tonow"

<block wx:for="{{searchReault}}" wx:key="item" style="overflow-y: scroll;">
    <navigator url="../now/index" class="weui-cell" hover-class="weui-cell_active"
       data-id="{{item.id}}" data-name="{{item.name}}" data-songUrl="{{item.mp3Url}}" data-picUrl="{{item.picUrl}}" 
       data-singer="{{item.singer}}"
       open-type="switchTab" bindtap="tonow">
       <view class="weui-cell__bd">
          <view class="song-name">{{item.name}}
               <text class="song-singer">{{item.singer}}</text>
            </view>
         </view>
       </navigator>
</block>

在tonow事件中,獲取當前的歌曲

tonow: function (event) {
        let songData = {
            id: event.currentTarget.dataset.id,
            name: event.currentTarget.dataset.name,
            mp3Url: event.currentTarget.dataset.songurl,
            picUrl: event.currentTarget.dataset.picurl,
            singer: event.currentTarget.dataset.singer
        }
        // 將當前點擊的歌曲保存在緩存中
        wx.setStorageSync('clickdata', songData)
        wx.switchTab({
            url: '../now/index'
        })
    }
  • 正在播放


佈局:歌曲封面,滑動條上下爲操做按鈕,
封面在採用圓角,rotate,transition既能夠
滑動快進:在滑動條上綁定事件 slider3change

//滑動 歌曲快進
function sliderToseek(e, cb) {
  wx.getBackgroundAudioPlayerState({
    success: function (res) {
      var dataUrl = res.dataUrl
      var duration = res.duration
      let val = e.detail.value
      let cal = val * duration / 100
      cb && cb(dataUrl, cal);
    }
  })
}
//分隔 在page中調用
  slider3change: function (e) {
    sliderToseek(e, function (dataUrl, cal) {
      wx.playBackgroundAudio({
        dataUrl: dataUrl
      })
      wx.seekBackgroundAudio({
        position: cal
      })
    })
  },

一個自定義的sliderToseek函數:

參數e 能夠獲取滑動的值,獲取正在播放的音樂信息成功後執行回調函數1->播放 回調函數2->跳到指定位置;
拆分歌詞:
在api中獲得的歌詞:"[00:00.00] 做曲 : 黃家駒 [00:01.00] 做詞 : 黃家駒 [00:18.580]今天我 寒夜裏看雪飄過 [00:25.050]懷着冷卻了的心窩漂遠方 [00:30.990]風雨裏追趕 "
在page外定義函數:

]劃分數組 第二部分就是歌詞內容:item.split(']')[1] 第一部分即爲對應的時間:item.split(']')[0]

// 獲取歌詞
function getlyric(id,cb) {
  console.log('id:',id)
  let url = `http://neteasemusic.leanapp.cn/lyric`
  wx.request({
    url: url,
    data: {
      id: id
    },
    method: 'GET', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
    // header: {}, // 設置請求的 header
    success: function (res) {
      // success
    
      if (!res.data.lrc.lyric) return false;
     
      let lyric = res.data.lrc.lyric
     
      let timearr = lyric.split('[')
      let obj = {}
      let lyricArr=[]
      // seek 爲鍵  歌詞爲value
      timearr.forEach((item) => {
        let key = parseInt(item.split(']')[0].split(':')[0]) * 60 + parseInt(item.split(']')[0].split(':')[1])
        let val = item.split(']')[1]
        
        obj[key] = val
      })
      for(let key in obj){
        // obj[key] = obj[key].split('\n')[0]
        lyricArr.push(obj[key])
      }
      cb&&cb(obj,lyricArr)
    },
    fail: function (res) {
      // fail
    },
    complete: function (res) {
      // complete
    }
  })
}

在page中調用:傳入歌曲ID(上文咱們已經存入緩存,在緩存中取出便可),和將其設置在data的回調

getlyric(id,function(data, lyricArr){
           that.setData({
             lyricobj:data,
             lyricArr:lyricArr
           })
         })

wxml進行渲染:

<!--歌詞-->
<view class="lyric-content" hidden="{{islyric}}" style="height:401px; overflow-y: scroll;"             
 bindtap="showCircle">
  <view class="lyric"  style="overflow-y: scroll;">
      <block wx:for="{{lyricArr}}" >

        <view> {{item}} </view>
      </block>
    </view>

</view>

添加歌曲:


個人能夠在本地緩存中添加兩個key入對應的信息

like:個人喜歡

recent:最近

選擇事件

radioChange: function(e) {
    console.log('radio發生change事件,攜帶value值爲:', e.detail.value)
    this.setData({
      percent:'100%'
    })
  },
  //radio發生change事件,攜帶value值爲: like
  //radio發生change事件,攜帶value值爲: recent

點擊添加按鈕,向上呼出選項,將當前播放的歌曲設置到對應的數組便可

進行當前歌曲的播放:
頁面onshow的時候,獲取本地緩存的信息,在success的回調中,設置到data,以供頁面解析,然後在獲取歌詞的函數中也進行一次回調,設置歌詞,
播放本地音樂,播放成功以後,在success的回調中,獲取正在播放的音樂信息,包括該歌曲的總時長,再進行設置。

onShow: function () {
    var that = this;
    console.log('正在播放 is on show')
    // 獲取緩存
    wx.getStorage({
      key: 'clickdata',
      success: function (res) {
        var value = res.data
        var id =  value.id
        if (value) {
          // 設置到data
          that.setData({
            id:id,
            name: value.name,
            src: value.mp3Url,
            poster: value.picUrl,
            author: value.singer
          })
         getlyric(id,function(data, lyricArr){
           that.setData({
             lyricobj:data,
             lyricArr:lyricArr
           })
         }) 
        }
        let url = that.data.src || value.mp3Url;
        // 播放
        wx.playBackgroundAudio({
          dataUrl: value.mp3Url,
          title: value.name,
          coverImgUrl: value.picUrl,
          success: function () {
            wx.hideLoading()
             console.log('url',url)
             setTimeout(function(){
                wx.getBackgroundAudioPlayerState({
                  success: function (res) {
                    var tempduration = res.duration
                    console.log('get bg success', tempduration, res)
                    // 設置時長
                    that.setData({
                      sumduration: tempduration
                    })
                  },
                  complete: function (res) {
                    console.log(' get bg complete:', res)
                  }
                })
             },1000)
          },
          complete:function(){
            // 獲取正在播放的信息
            console.log('play',url)
         
          }
        })
      }
    })
},

這樣咱們不知不覺進入多個回調嵌套的問題

代碼優化,使用Promise,較爲優雅地解決回調

小程序暫時不支持async await

在 common.js 中爲小程序提供的api上裹上一層Promise,而且經過module.exports = operation暴露出去

const operation = {
    getMusicData: function () {
        return new Promise((resolve, reject) => {
            wx.getBackgroundAudioPlayerState({
                success: function (res) {
                    resolve(res);
                },
                fail: function (err) {
                    reject(err);
                }
            })
        })
    },
    // 播放音樂 參數:url title 圖片url
    playMusic: function (url, title, pic) {
        return new Promise((resolve, reject) => {
            wx.playBackgroundAudio({
                dataUrl: url,
                title: title,
                coverImgUrl: pic,
                success: function () {
                    resolve(true)
                },
                fail: function () {
                    reject(new Error('播放錯誤'));
                }
            })
        })
    },
    asyncGetStorage: function (key) {
        return new Promise((resolve, reject) => {
            wx.getStorage({
                key: key,
                success: function (res) {
                    resolve(res.data)
                },
                fail: function (err) {
                    reject(err)
                }
            })
        })
    },
    getlyric: function (id) {
        return new Promise((resolve, reject) => {
            console.log('id:', id)
            let url = `http://neteasemusic.leanapp.cn/lyric`
            wx.request({
                url: url,
                data: {
                    id: id
                },
                method: 'GET', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
                // header: {}, // 設置請求的 header
                success: function (res) {
                    // success
                    if (!res.data.lrc.lyric) return false;
                    let lyric = res.data.lrc.lyric
                    let timearr = lyric.split('[')
                    let obj = {}
                    let lyricArr = []
                    // seek 爲鍵  歌詞爲value
                    timearr.forEach((item) => {
                        let key = parseInt(item.split(']')[0].split(':')[0]) * 60 + parseInt(item.split(']')[0].split(':')[1])
                        let val = item.split(']')[1]
                        obj[key] = val
                    })
                    for (let key in obj) {
                        // obj[key] = obj[key].split('\n')[0]
                        lyricArr.push(obj[key])
                    }
                    // cb && cb(obj, lyricArr)
                    resolve(lyricArr)
                },
                fail: function (err) {
                    reject(err)
                },
                complete: function (res) {
                    // complete
                }
            })
        })
    }
}
module.exports = operation

重寫一下當前歌曲播放事件

onShow: function () {
    let that = this;
    Common.asyncGetStorage('clickdata')//本地緩存
      .then(data => {
        // console.log(data)
        if (!data) return;
        that.setData({
          id: data.id,
          name: data.name,
          src: data.mp3Url,
          poster: data.picUrl,
          author: data.singer
        })
        return Common.playMusic(data.mp3Url,  data.name, data.picUrl);
      })
      .then(status => {
        if(!status) return;
        wx.hideLoading();
        console.log('id,',that.data.id)
        return Common.getlyric(that.data.id)
      })
      .then((lyricArr) => {
        console.log('lyricArr',lyricArr)
        that.setData({
          lyricArr: lyricArr
        })
        return Common.getMusicData()
      })
      .then(data => {
        let tempduration = data.duration
        console.log('get bg success', tempduration, data)
        // 設置時長
        that.setData({
          sumduration: tempduration
        })
      })
},

這樣便可縮減部分代碼。

有幫助能夠Star>github源碼


18屆小前端求職中['html/html5', 'css/css3', 'js/es5/es6', 'node']
1424254461@qq.com

相關文章
相關標籤/搜索