用戶
 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

掃一掃,登錄網站

小程序社區 首頁 教程 實戰教程 查看內容

仿網易云音樂微信小程序

Rolan 2019-6-27 00:37

項目部分截圖(Gif)前言前一陣子學習了微信小程序,為了鞏固所學的知識和提高實戰經驗,決定自己手擼一款小程序。因為聽歌一直在用網易云音樂,所以突發奇想就做一款仿網易云音樂的小程序吧!開發中遇到了很多在學習 ...

項目部分截圖(Gif)


前言

前一陣子學習了微信小程序,為了鞏固所學的知識和提高實戰經驗,決定自己手擼一款小程序。因為聽歌一直在用網易云音樂,所以突發奇想就做一款仿網易云音樂的小程序吧!開發中遇到了很多在學習中沒有遇到過的坑,也很感謝在我改不出BUG時給予幫助的老師學長同學!本著學習和分享的目的,寫下以下的文字,希望能給初學小程序的你帶來一點幫助,大佬輕點噴。


#開發前準備


tabBar部分

自定義tabBar

一般在開發中,微信小程序給我們的tabBar就能滿足需求。但是,有些特別的需求必須使用自定義tabBar才能滿足。 比如tabBar實現半透明。那么,如何才能自定義tabBar呢?
1.首先,在 app.json里的"tabBar"里聲明 "tabBar": { "custom": true }
2.接著在項目的根目錄下新建一個custom-tab-bar文件夾。里面包含index.wxml index.js index.json index.wxss四個文件。更多細節參考微信小程序文檔developers.weixin.qq.com/miniprogram…




"tab-bar">
   "tab-bar-border">

  'tab-bar-item' >
    '../images/music.png' hidden='{{isShow_index}}' bindtap='switchTab_index'>
    '../images/selected-music.png' hidden='{{!isShow_index}}' bindtap='switchTab_index'>
    "color:{{isShow_index ? selectedColor : color}}">樂庫
  


    'tab-bar-item' bindtap='switchTab_playing'>
    '../images/selected-playing.png' hidden='{{isShow_playing}}'>
    '../images/playing.png' hidden='{{!isShow_playing}}'>
    
  


    'tab-bar-item' bindtap='switchTab_me'>
    '../images/me.png' hidden='{{isShow_me}}'>
    '../images/selected-me.png' hidden='{{!isShow_me}}'>
    "color:{{isShow_me ? selectedColor : color}}">我的
  

復制代碼
// index.js
Component({
  data: {
    isShow_index:true,
    isShow_playing:false,
    isShow_me:false,
    selected: 0, //首頁
    color: "#8D8D8D",
    selectedColor: "#C62F2F",
    list: [{
      pagePath: "/pages/index/index",
      iconPath: "/images/music.png",
      selectedIconPath: "/images/selected-music.png",
      text: "樂庫"
    }, {
      pagePath: "/pages/love/love",
        iconPath: "/images/selected-playing.png",
      selectedIconPath: "/images/playing.png",
      text: ""
    },
      {
        pagePath: "/pages/me/me",
        iconPath: "/images/me.png",
        selectedIconPath: "/images/selected-me.png",
        text: "我的"
      }]
  },

  methods: {
    switchTab_index:function(){
      wx.switchTab({
        url:'/pages/index/index'
      })
      this.setData({
        isShow_index: true,
        isShow_me: false,
        isShow_playing: false
      })
    },
    switchTab_playing: function () {
      wx.switchTab({
        url: '/pages/love/love'
      })
      this.setData({
        isShow_playing: true,
        isShow_index: false,
        isShow_me: false
      })
    },
    switchTab_me: function () {
      wx.switchTab({
        url: '/pages/me/me'
      })
      this.setData({
        isShow_me:true,
        isShow_playing: false,
        isShow_index: false
      })
    }
  }
})
復制代碼

tabBar半透明

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">

/* custom-tab-bar/index.wxss */
.tab-bar {
  height:7%;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: 48px;
  background:#FAFBFD;
  opacity: 0.93;
  display: flex;
  padding-bottom: env(safe-area-inset-bottom);
}
復制代碼

API封裝

一般我們https請求都是通過wx.request來請求,但是這種方法只能請求一次數據,如果首頁用wx.request來請求的話,代碼看起來會很冗長和雜亂。不僅自己容易搞糊涂,其他人看代碼時也會很累。因此為了代碼的整潔干凈,我在這里新建了一個文件專門存放API。一般在根目錄下的utils文件夾下新建一個api.js,但我在根目錄下新建了文件夾API,里面包含api.js

// api.js
const API_BASE_URL = 'http://musicapi.leanapp.cn';
const request = (url, data) => { 
  let _url = API_BASE_URL  + url;
  return new Promise((resolve, reject) => {
    wx.request({
      url: _url,
      method: "get",
      data: data,
      header: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      success(request) {
        resolve(request.data)
        
      },
      fail(error) {
        reject(error)
      }
    })
  });
}


module.exports ={
  gethotsongs:(data) =>{
    return request('/search/hot',data)//熱搜接口
  },
  searchSuggest:(data)=>{
    return request('/search/suggest',data)//搜索建議接口
  },
  searchResult:(data)=>{
    return request('/search',data)//搜索結果接口
  },
  getBanner:(data)=>{
    return request('/banner',data)//個性推薦輪播
  },
  getsongsheet:(data)=>{
    return request('/top/playlist',data)//熱門歌單接口
  },
  getNewSong:(data)=>{
    return request('/personalized/newsong',data)//最新音樂接口
  },
  getDjRadios:(data)=>{
    return request('/dj/recommend',data)//電臺推薦接口
  },
  getProgramRecommend:(data)=>{
    return request('/program/recommend',data)//推薦節目接口
  },
  getRecommendType:(data)=>{
    return request('/dj/recommend/type',data)//所有電臺分類推薦
  },
  getRecommendMV:(data)=>{
    return request('/personalized/mv',data)//推薦MV
  },
  getNewMv:(data)=>{
    return request('/mv/first',data)//最新MV
  },
  getNewEst:(data)=>{
    return request('/album/newest',data)//最新專輯
  },
  getTopList:(data)=>{
    return request('/top/list',data)//排行榜
  },
  getDjList:(data)=>{
    return request('/dj/catelist',data) //電臺分類
  },
  getPay:(data)=>{
    return request('/dj/paygift',data)//付費精品
  },
  getSonger:(data)=>{
    return request('/toplist/artist',data)//歌手排行
  }
}
復制代碼

api.js只能通過module.exports來暴露,那個頁面要數據就從這拿。如果在哪個頁面要用到它,還需要在頭部引入一下:

const API = require('../../API/api')

以個性推薦輪播圖為例,

  getBanner: function() {
    API.getBanner({
      type: 2
    }).then(res => {
      if (res.code === 200) { //更加嚴謹
        this.setData({
          banner: res.banners
        })
      }
    })
  }
復制代碼

這樣就把請求到的數據存儲到banner中了。


搜索部分

輸入框樣式

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
我這里是引入了WEUI的樣式, 1.下載weui.wxss,鏈接我找不到了,所以我放上了我的github上的weui.wxss。 2.把下載好的weui.wxss放到根目錄下。 3.在app.wxss@import "weui.wxss";引入一下就可以使用微信提供給我們的樣式了。 4.WeUI樣式庫

熱門搜索

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
上面已經提到我從api.js中拿數據。

 // 從接口到獲取到數據導入到hotsongs
  gethotsongs() {
    API.gethotsongs({ type: 'new' }).then(res => {
      wx.hideLoading()
      if (res.code === 200) {  //嚴謹
        this.setData({
          hotsongs: res.result.hots
        })
      }
    })
  }
復制代碼

搜索歷史

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
思路:當在輸入框輸入完成后-->失去焦點--> 利用wx.setStorageSync存進緩存中-->wx.getStorageSync獲取到并把它打印出來。

  // input失去焦點函數
  routeSearchResPage: function(e) {
    console.log(e.detail.value)
    let history = wx.getStorageSync("history") || [];
    history.push(this.data.searchKey)
    wx.setStorageSync("history", history);
  },

//每次顯示變動就去獲取緩存,給history,并for出來。
  onShow: function () {
    this.setData({
      history: wx.getStorageSync("history") || []
    })
  },
復制代碼

清空搜索歷史

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
思路:×圖標綁定事件->呼出對話框wx.showModal->確定則把history賦值為空

  // 清空page對象data的history數組 重置緩存為[]
clearHistory: function() {
  const that = this;
  wx.showModal({
    content: '確認清空全部歷史記錄',
    cancelColor:'#DE655C',
    confirmColor: '#DE655C',
    success(res) {
      if (res.confirm) {
        that.setData({
          history: []
        })
        wx.setStorageSync("history", []) //把空數組給history,即清空歷史記錄
      } else if (res.cancel) {
      }
    }
  })
},
復制代碼

實時搜索建議

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
思路:實時獲取輸入框的值->把值傳給搜索建議API,發起網絡請求->請求之后拿到搜索建議->打印結果并隱藏其他組件只保留搜索建議的組件(類似于Vue里的v-show)

 //獲取input文本并且實時搜索,動態隱藏組件
  getsearchKey:function(e){
    console.log(e.detail.value) //打印出輸入框的值
    let that = this;
    if(e.detail.cursor != that.data.cursor){ //實時獲取輸入框的值
      that.setData({
        searchKey: e.detail.value
      })
    }
    if(e.value!=""){ //組件的顯示與隱藏
      that.setData({
        showView: false
      })
    } else{
      that.setData({
        showView: ""
      })
    }
    if(e.detail.value!=""){ //解決 如果輸入框的值為空時,傳值給搜索建議,會報錯的bug
      that.searchSuggest();
    }  
  }
復制代碼
// 搜索建議
searchSuggest(){
  API.searchSuggest({ keywords: this.data.searchKey ,type:'mobile'}).then(res=>{
    if(res.code === 200){
      this.setData({
        searchsuggest:res.result.allMatch
      })
    }
  })
}
復制代碼

點擊熱搜或歷史,執行搜索

思路:關鍵是event,點擊通過e.currentTarget.dataset.value拿到所點擊的值,再交給其他方法執行搜索行為。

// 點擊熱門搜索值或搜索歷史,填入搜索框
  fill_value:function(e){
    let that = this;
    console.log(history)
    // console.log(e.currentTarget.dataset.value)
    that.setData({
      searchKey: e.currentTarget.dataset.value,//點擊吧=把值給searchKey,讓他去搜索
      inputValue: e.currentTarget.dataset.value,//在輸入框顯示內容
      showView:false,//給false值,隱藏 熱搜和歷史 界面
      showsongresult: false, //給false值,隱藏搜索建議頁面
    })
    that.searchResult(); //執行搜索功能
  }
復制代碼

搜索結果

思路:輸入結束->確認鍵->調用searchResult請求到結果

// 搜索完成點擊確認
  searchover:function(){
    let that = this;
    that.setData({
      showsongresult: false
    })
    that.searchResult();
  }
復制代碼
 // 搜索結果
searchResult(){
  console.log(this.data.searchKey)
  API.searchResult({ keywords: this.data.searchKey, type: 1, limit: 100, offset:2 }).then(res => {
    if (res.code === 200) {
      this.setData({
        searchresult: res.result.songs
      })
    }
  })
}
復制代碼

樂庫部分

樂庫部分其實沒什么邏輯很難的部分,以結構和樣式為主,在這里就不贅述了。可以到我的github上查看。在這里分享一些小功能的實現和踩到的坑。

個性推薦,主播電臺切換

1.個性推薦和主播電臺是兩個swiper-item所以他們才可以左右滑動,就像輪播圖一樣,不過輪播圖放的是圖片,而這里放的是整個頁面。 2.我要實現的效果是左右滑動的同時,個性推薦主播電臺下面的白色方塊也要跟著滑動。
1. 第一種方法
給包裹兩個swiper-itemswiper添加一個bindchange="changeline"事件,把事件對象event打印出來發現,console.log(e.detail.current),當我們左右滑動的時候cuurrent的值會在01之間切換。所以我給白色方塊添加

class="{{changeline?'swiper_header_line_before':'swiper_header_line_after'}}"
復制代碼
    if(e.detail.current === 0){
    this.setData({
       changeline:true
      })
    }else{
    this.setData({
       changeline:false
      })
    }
復制代碼

current為0,即頁面在個性推薦時,讓changelinetrue;當current為1,即頁面在主播電臺時,讓changelinefalse;為true時,給白色方塊加持swiper_header_line_before的樣式,為false時,加持swiper_header_line_after的樣式。這樣就可以跟隨swiper-item的滑動而切換了。但是,這種切換方式太僵硬了,沒有那種流暢的切換效果,而且不適合多swiper-item頁面。
2. 第二種方法

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
讓一半寬度,四分之一寬度設置為變量是為了兼容不同的手機型號。因為寫死數據肯定會有BUG,所以才要計算寬度。

"weui-navbar-slider" style="transform:translateX({{slideOffset}}px);">
復制代碼
.weui-navbar-slider{
  width:28px;
  height: 5px;
  background: #ffffff;
  border-radius:10rpx;
  transition: transform .6s;
 }
復制代碼

slideOffset為變量,動態接受從data傳來的數據。

onLoad:function(){
    wx.getSystemInfo({
      success: function (res) {
        // console.log(res.windowWidth)
        // console.log(res.windowWidth / 2 / 2)
        half = res.windowWidth / 2 ;
        quarter = res.windowWidth / 2 / 2;
        that.setData({
          slideOffset: quarter - 14 //onLoad的時候讓 quarter - 14 給slideOffset,即一開始就讓他在個性推薦的下面,否則onLoad的時候一開始在0的位置
        })
      }
    })
}

  changeline:function(e){
    // console.log(e)
    // console.log(e.detail.current)
    let current = e.detail.current; //獲取swiper的current值
    if(e.detail.current === 0){
      this.setData({
        slideOffset: quarter - 14
      })
    }
    if(e.detail.current === 1){
      this.setData({
        slideOffset: (quarter - 14) + half
      })
    }
    if(e.detail.current === null){
      this.setData({
        slideOffset: quarter - 14
      })
    }
  }
復制代碼

MV播放

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
主要是結構和樣式,我直接上代碼了。


"mv_box">
    


"mv_name">{{mv.name}}
"mv_time"> 發行:  {{mv.publishTime}}
"mv_time mv_times">播放次數:  {{mv.playCount}}
"mv_time mv_desc">{{mv.desc}}
"mv_time mv_desc mv_other">點贊: {{mv.likeCount}}
"mv_time mv_desc mv_other">收藏: {{mv.subCount}}
"mv_time mv_desc mv_other">評論: {{mv.commentCount}}
"mv_time mv_desc mv_other">分享: {{mv.shareCount}}
復制代碼
/* play/play_mv.wxss */
.mv_box{
    width: 100%;
    height: 480rpx;
    margin-top:-2rpx;
}
.mv{
    width: 100%;
    height: 100%;
    border-radius:15rpx;
}
.mv_name{
    margin-top:20rpx;
    margin-left:20rpx;
}
.mv_time{
    font-size: 12px;
    margin-left:20rpx;
    color:#979798;
    display:initial;
}
.mv_times{
    margin-left: 100rpx;
}
.mv_desc{
    display: block;
    color:#6A6B6C;
}
.mv_other{
    display: block;
}
復制代碼
// play_mv.js
const API_BASE_URL = 'http://musicapi.leanapp.cn';
const app = getApp();
Page({
  data: {
    mv: [],
    autoplay: true,
    loop: true,
    showfullscreenbtn: true,
    showcenterplaybtn: true,
    enableprogressgesture: true,
    showmutebtn: true,
    objectfit: 'contain',
  },
  onLoad: function (options) {
    // console.log(mv_url);
    const mvid = options.id; // onLoad()后獲取到歌曲視頻之類的id

    // 請求MV的地址,失敗則播放出錯,成功則傳值給createBgAudio(后臺播放管理器,讓其后臺播放)
    wx.request({
      url: API_BASE_URL + '/mv/detail',
      data: {
        mvid: mvid    
      },
      success: res => {
        console.log(res.data.data.brs['480'])
        console.log('歌曲音頻url:', res)
        if (res.data.data.brs === null) {  //如果是MV 電臺 廣告 之類的就提示播放出錯,并返回首頁
          console.log('播放出錯')
          wx.showModal({
            content: '服務器開了點小差~~',
            cancelColor: '#DE655C',
            confirmColor: '#DE655C',
            showCancel: false,
            confirmText: '返回',
            complete() {
              wx.switchTab({
                url: '/pages/index/index'
              })
            }
          })
        } else {
          this.setData({
            mv: res.data.data
          })
        }
      }
    })
  },
})
復制代碼

歌手榜

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">

// 歌手榜的js
const API = require('../../API/api');
const app = getApp();
Page({

  data: {
    songers: [], //歌手榜
  },

  onLoad: function (options) {
    wx.showLoading({
      title: '加載中',
    });
    this.getSonger();
  },

  getSonger: function () {
    API.getSonger({}).then(res => {
      wx.hideLoading()
      this.setData({
        songers: res.list.artists.slice(0, 100)
      })
    })
  },
  handleSheet: function (event) { //event 對象,自帶,點擊事件后觸發,event有type,target,timeStamp,currentTarget屬性
    const sheetId = event.currentTarget.dataset.id; //獲取到event里面的歌曲id賦值給audioId
    wx.navigateTo({                                 //獲取到id帶著完整url后跳轉到play頁面
      url: `./moremore_songer?id=${sheetId}`
    })
  },
})
復制代碼

for="{{songers}}" wx:key="" class='songer_box' data-id="{{item.id}}" bindtap='handleSheet'>
  'songer_index_box'>
    'songer_index'>{{index + 1}}
  
  'songer_img_box'>
  "{{item.picUrl}}" class='songer_img'>
  
  'songer_name_box'>
  'songer_name'>{{item.name}}
  'songer_score'>{{item.score}}熱度
  

復制代碼
// 歌手下級路由歌曲列表
const API_BASE_URL = 'http://musicapi.leanapp.cn';
const app = getApp();
Page({
  data: {
    songList: []
  },
  onLoad: function (options) {
    wx.showLoading({
      title: '加載中',
    });
    const sheetId = options.id;
    wx.request({
      url: API_BASE_URL + '/artists',
      data: {
        id: sheetId    
      },
      success: res => {
        const waitForPlay = new Array;
        for (let i = 0; i <= res.data.hotSongs.length - 1; i++) { //循環打印出其id
          waitForPlay.push(res.data.hotSongs[i].id) //循環push ID 到waitForPlay數組
          app.globalData.waitForPlaying = waitForPlay  //讓waitForPlay數組給全局數組
          // console.log(app.globalData.waitForPlaying)
        }
        wx.hideLoading()
        console.log(res.data.hotSongs)
        this.setData({
          songList: res.data.hotSongs
        })
      }
    })
  },
  handlePlayAudio: function (event) { //event 對象,自帶,點擊事件后觸發,event有type,target,timeStamp,currentTarget屬性
    const audioId = event.currentTarget.dataset.id; //獲取到event里面的歌曲id賦值給audioId
    wx.navigateTo({                                 //獲取到id帶著完整url后跳轉到play頁面
      url: `../../play/play?id=${audioId}`
    })
  }
})
復制代碼

'search_result_songs'>
  for="{{songList}}" wx:key="" class='search_result_song_item songer_box' data-id="{{item.id}}" bindtap='handlePlayAudio'>
    'songer_index_box'>
      'songer_index'>{{index + 1}}
    
    'songer_img_box'>
      'search_result_song_song_name'>{{item.name}}
      'search_result_song_song_art-album'>{{item.ar[0].name}} - {{item.al.name}}
    
  

復制代碼

推薦歌單

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
因為樣式與排行榜類似,所以只放出圖片,源碼可以到我的github上查看。

榜單排行

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
請查看源碼

換一換功能

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
思路:綁定點擊事件->選取隨機的三個數->給空值->push三個隨機數進數組中->重新賦值。

  // 換一換
  change_1:function(){
    let maxNum = this.data.more_recommend_create.length  //計算數據長度
    let r1 = parseInt(Math.random() * (maxNum - 0) + 0); //取【0-數據長度】內的整數隨機數
    let r2 = parseInt(Math.random() * (maxNum - 0) + 0);
    let r3 = parseInt(Math.random() * (maxNum - 0) + 0);
    this.setData({
      recommend_create: []
    })
    //重新取3組數據
    this.data.recommend_create.push(this.data.more_recommend_create[r1])
    this.data.recommend_create.push(this.data.more_recommend_create[r2])
    this.data.recommend_create.push(this.data.more_recommend_create[r3])
    //重新賦值
    this.setData({
      recommend_create: this.data.recommend_create
    })
  }
復制代碼

播放界面

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
圖片太大,因此加快了播放。

播放功能

思路:利用data-id="{{item.id}}"獲取到歌曲ID放在event中-> 通過event對象事件獲取ID并跳轉到播放頁面 ->wx.request獲取到歌曲的音頻地址及detail->背景音頻管理器 wx.getBackgroundAudioManager()->播放
以歌手榜下級路由歌曲列表為例,

for="{{songList}}" wx:key="" class='search_result_song_item songer_box' data-id="{{item.id}}" bindtap='handlePlayAudio'>
復制代碼
  handlePlayAudio: function (event) { //event 對象,自帶,點擊事件后觸發,event有type,target,timeStamp,currentTarget屬性
    const audioId = event.currentTarget.dataset.id; //獲取到event里面的歌曲id賦值給audioId
    wx.navigateTo({                                 //獲取到id帶著完整url后跳轉到play頁面
      url: `../../play/play?id=${audioId}`
    })
  }
復制代碼
// play.js
const API_BASE_URL = 'http://musicapi.leanapp.cn';
const app = getApp();
Page({
data: {
  isPlay: '',
  song:[],
  innerAudioContext: {},
  show:true,
  showLyric:true,
  songid:[],
  history_songId:[]
},
onLoad: function (options) {
  const audioid = options.id; // onLoad()后獲取到歌曲視頻之類的id
  this.play(audioid); //把從wxml獲取到的值傳給play()
},
play: function (audioid){
  const audioId = audioid;
  app.globalData.songId = audioId;  //讓每一個要播放的歌曲ID給全局變量的songId
  const innerAudioContext = wx.createInnerAudioContext();
  this.setData({
    innerAudioContext,
    isPlay: true
  })
  // 請求歌曲音頻的地址,失敗則播放出錯,成功則傳值給createBgAudio(后臺播放管理器,讓其后臺播放)
  wx.request({
    url: API_BASE_URL + '/song/url',
    data: {
      id: audioId
    },
    success: res => {
      if (res.data.data[0].url === null) {  //如果是MV 電臺 廣告 之類的就提示播放出錯,并返回首頁
        wx.showModal({
          content: '服務器開了點小差~~',
          cancelColor: '#DE655C',
          confirmColor: '#DE655C',
          showCancel: false,
          confirmText: '返回',
          complete() {
            wx.switchTab({
              url: '/pages/index/index'
            })
          }
        })
      } else {
        this.createBgAudio(res.data.data[0]);
      }
    }
  })
  //獲取到歌曲音頻,則顯示出歌曲的名字,歌手的信息,即獲取歌曲詳情;如果失敗,則播放出錯。
  wx.request({
    url: API_BASE_URL + '/song/detail',
    data: {
      ids: audioId    //必選參數ids
    },
    success: res => {
      if (res.data.songs.length === 0) {
        wx.showModal({
          content: '服務器開了點小差~~',
          cancelColor: '#DE655C',
          confirmColor: '#DE655C',
          showCancel: false,
          confirmText: '返回',
          complete() {
            wx.switchTab({
              url: '/pages/index/index'
            })
          }
        })
      } else {
        this.setData({
          song: res.data.songs[0],  //獲取到歌曲的詳細內容,傳給song
        })
        app.globalData.songName = res.data.songs[0].name;
      }
    },
  })
},
createBgAudio(res) {
  const bgAudioManage = wx.getBackgroundAudioManager(); //獲取全局唯一的背景音頻管理器。并把它給實例bgAudioManage
  app.globalData.bgAudioManage = bgAudioManage;         //把實例bgAudioManage(背景音頻管理器) 給 全局
  bgAudioManage.title = 'title';                        //把title 音頻標題 給實例
  bgAudioManage.src = res.url;                          // res.url 在createBgAudio 為 mp3音頻  url為空,播放出錯
  const history_songId = this.data.history_songId
  const historySong = {
    id: app.globalData.songId,
    songName:app.globalData.songName
  }
  history_songId.push(historySong)
  bgAudioManage.onPlay(res => {                         // 監聽背景音頻播放事件
    this.setData({
      isPlay: true,
      history_songId
    })
  });
  bgAudioManage.onEnded(() => {                  //監聽背景音樂自然結束事件,結束后自動播放下一首。自然結束,調用go_lastSong()函數,即歌曲結束自動播放下一首歌
    this.go_lastSong();
  })
  wx.setStorageSync('historyId', history_songId); //把historyId存入緩存
},
})
復制代碼

暫停/播放

  
"play_suspend">
  "icon_playing">"handleToggleBGAudio" src="../images/suspend.png" hidden="{{!isPlay}}" class="{{'img_play_suspend'}}" />  
  "handleToggleBGAudio" src="../images/play.png" hidden="{{isPlay}}" class="{{'img_play_suspend'}}" /> 

復制代碼
// 播放和暫停
handleToggleBGAudio() {
  // const innerAudioContext = app.globalData.innerAudioContext;
  const bgAudioManage = app.globalData.bgAudioManage;
  const {isPlay} = this.data;
  if (isPlay) {
    bgAudioManage.pause();
    // innerAudioContext.pause();handleToggleBGAudio
  } else {
    bgAudioManage.play();
    // innerAudioContext.play();
  }
  this.setData({
    isPlay: !isPlay
  })
  console.log(this.data.isPlay)
}
復制代碼

上一首/下一首(隨機播放)

思路:點擊歌單或歌手頁,獲取到對應的歌單/歌手id->wx.request請求數據獲取到所有的歌單內/歌手熱門歌曲音頻地址->給全局變量globalData->點擊上一首/下一首隨機獲取到全局變量的一則數據->給play()方法->播放


  onLoad: function (options) {
  wx.showLoading({
    title: '加載中',
  });
  const sheetId = options.id;
  wx.request({
    url: API_BASE_URL + '/playlist/detail',
    data: {
      id: sheetId    
    },
    success: res => {
      const waitForPlay = new Array;
      for (let i = 0; i <= res.data.playlist.trackIds.length - 1;i++){ //循環打印出其id
        waitForPlay.push(res.data.playlist.trackIds[i].id) //循環push ID 到waitForPlay數組
        app.globalData.waitForPlaying = waitForPlay  //讓waitForPlay數組給全局數組
      }
      wx.hideLoading()
      this.setData({
        songList: res.data.playlist.tracks
      })  
    }
  })
}
復制代碼
"icon_playing ">"../images/lastSong.png" class=" icon_play" bindtap="go_lastSong" />
"icon_playing ">"../images/nextSong.png" class=" icon_play" bindtap="go_lastSong" />
復制代碼
  go_lastSong:function(){ 
  let that = this;
  const lastSongId = app.globalData.waitForPlaying;
  const songId = lastSongId[Math.floor(Math.random() * lastSongId.length)]; //隨機選取lastSongId數組的一個元素
  that.data.songid = songId;
  this.play(songId)//傳進play()方法中
  app.globalData.songId=songId;
}
復制代碼

歌詞/封面切換

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
因為網易云API的歌詞接口崩潰,請求不到歌詞,所以我只能把歌詞寫死為純音樂,請欣賞。類似于v-show

 

"sing-show" bindtap="showLyric" >
  "moveCircle {{isPlay ? 'play' : ''}}" hidden="{{!showLyric}}">
    "{{song.al.picUrl}}" class="coverImg {{isPlay ? 'play' : ''}}" hidden="{{!showLyric}}"/>
  
  

復制代碼
  // 點擊切換歌詞和封面
showLyric(){
  const {showLyric} = this.data;
  this.setData({
    showLyric: !showLyric
  })
}
復制代碼

破產版的孤獨星球動效

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
封面旋轉:

@keyframes rotate {
  0%{
    transform: rotate(0);
  }
  100%{
    transform: rotate(360deg);
  }
}
復制代碼

擴散的圓形線條: 其實就是外面套一個盒子,盒子邊框變大以及透明度逐漸變低。

@keyframes moveCircle {
  0%{
    width: 400rpx;
    height: 400rpx;
    border: 1px solid rgba(255, 255, 255, 1)
  }
  30%{
    width: 510rpx;
    height: 510rpx;
    border: 1px solid rgba(255, 255, 255, 0.8)
  }
  50%{
    width: 610rpx;
    height: 610rpx;
    border: 1px solid rgba(255, 255, 255, 0.6)
  }
  80%{
    width: 700rpx;
    height: 700rpx;
    border: 1px solid rgba(255, 255, 255, 0.4)
  }
  99%{
    width: 375px;
    height: 375px;
    border: 1px solid rgba(255, 255, 255, 0.1)
  }
  100%{
    width: 0px;
    height: 0px;
    border: 1px solid rgba(255, 255, 255, 0)
  }
}
復制代碼

背景毛玻璃


"{{song.al.picUrl}}" class="background_img" >
復制代碼
/* 播放界面毛玻璃效果 */
.background_img{ 
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
  filter: blur(20px);
  z-index: -1;
  transform: scale(1.5); /*和網易云音樂對比了一下,發現也是放大1.5倍*/
}
復制代碼

播放tabBar

" style="width: auto; height: auto; border-style: none; max-height: none; margin: 0px; max-width: 100%; visibility: visible; background-color: rgb(248, 249, 250); background-position: 50% center; background-repeat: no-repeat; cursor: zoom-in;">
思路是參考酷狗音樂小程序。這個tabBar的js,wxml與播放功能界面的js,wxml相同。因為音樂播放是用wx.getBackgroundAudioManager()背景音頻播放器管理的,所以才能同步。


我的tabBar

播放歷史

思路:play.js中一旦播放成功就把歌名及歌曲ID傳入全局變量->push到play.js里的數組中->wx.setStorageSync把數據存入緩存->在需要的頁面wx.getStorageSync獲取到緩存。


const history_songId = this.data.history_songId
const historySong = {
      // id: res.id
      id: app.globalData.songId,
      songName:app.globalData.songName
    }
    history_songId.push(historySong)
    wx.setStorageSync('historyId', history_songId); //把historyId存入緩存
復制代碼

 onShow:function(){
    var history = wx.getStorageSync('historyId');
    // console.log(history)
     this.setData({
      hidden:true,
      //  historyId: app.globalData.songName
       historyId: history
    })
    console.log(this.data.historyId)
  }
復制代碼

結語

做項目的過程總的來說痛并快樂,因為改不出BUG的樣子真的很狼狽,但實現了某一個功能的那一刻真的很欣慰。再次感謝給予幫助的老師學長同學。如果你喜歡這篇文章或者可以幫到你,不妨點個贊吧!同時也非常希望看到這篇文章的你在下方給出建議!最后奉上源碼,有需要的可以自取。最后,說點題外話,因為我是2020屆畢業生,現在面臨實習壓力,有沒有大佬撈一下。

鮮花
鮮花
雞蛋
雞蛋
分享至 : QQ空間
收藏
原作者: 你喜歡吃青椒么 來自: 掘金
致青春APP