网易云音乐小程序实践(taro+hooks+ts)

1,710 阅读2分钟

简介

鉴于一直找包含了taro+hooks+ts的项目未果,所以自己写了一个,大概花了两周时间,只完成了一些基本的功能。后台用的NeteaseCloudMusicApi开源接口。状态管理redux redux-saga

源码地址:github.com/v1nkon/taro…

一、环境准备

搭建网易云后台

    git clone git@github.com:Binaryify/NeteaseCloudMusicApi.git
    npm install
    node app.js

搭建小程序

    git clone git@github.com:v1nkon/taro_music.git
    npm install
    npm run dev:weapp

二、主要功能

  • 首页展示
  • 歌单列表
  • 歌曲播放页面
  • 搜索页

三、核心代码

整个项目逻辑比较复杂的部分应该就是歌曲播放的页面咯

播放页面

进入播放页面需要通过songId请求歌曲

  let [showLyric, setShowLyric] = useState(false)

  useEffect(()=> {
    // loadingSongDetail(1446522620)
    //如果当前歌曲不存在 且是请求新歌的时候  需要请求
    if( notSameSongOrNotExist(this.$router.params.songId) ){
      loadingSongDetail(this.$router.params.songId )
    }
  }, [])

  useEffect(()=>{
    song && Taro.setNavigationBarTitle({
      title: song.name
    });
  }, [song])

音乐播放组件(Audio)

包含了事件监听,歌词滚动,歌曲拖动等

let { song, preSong, playSongList, isPlay, togglePlay, setShowLyric, showLyric } = props

  let [curLong, setCurLong] = useState(0)
  let [isChanging, setIsChanging] = useState(false)
  let [songLyric, setSongLyric] = useState<Array<LYRIC>>([])
  let [viewId, setViewId] = useState("z1")
  let [showMusicList, setShowMusicList] = useState(false)


  useEffect( () => {
    if( song && song.url ){
      
      setSongLyric( formatLyric(song.lyric!) )
      innerAudioContext.src = song.url
      setCurLong(0)
      togglePlay(true)
    }else if(song && !song.url){
      setCurLong(0)
      Taro.showToast({
        title:'当前歌曲无权限\r 自动跳到下一首',
        icon:'none',
        duration:2000
      })
      setTimeout(()=>{
        audioToggleSong(1)
      },3000)
      
    }
  } , [song, preSong, setCurLong])


  let timeUpdate = useCallback(() => {
    isChanging || setCurLong( innerAudioContext.currentTime )
  }, [setCurLong, isChanging])

  useEffect( () => {
    onPlay()
    innerAudioContext && innerAudioContext.onTimeUpdate( timeUpdate )
    return () => {
    offPlay()
    innerAudioContext && innerAudioContext.offTimeUpdate( timeUpdate )
    }
  } ,[innerAudioContext, timeUpdate, onPlay, offPlay])

  useEffect( () => {
    offEnded()
    innerAudioContext && innerAudioContext.onEnded( audioToggleSong)
    return () => {
      innerAudioContext && innerAudioContext.offEnded( audioToggleSong )
      onEnded()
    }
  } , [innerAudioContext, audioToggleSong, offEnded, onEnded])




  let slideChange = useCallback( ({detail}) => {
    setCurLong( detail.value )
    innerAudioContext.stop()
    innerAudioContext.seek( detail.value )
    setIsChanging(false)
    setTimeout(()=>{
      innerAudioContext.play()
    }, 100)
  } , [song, setIsChanging, innerAudioContext])

  let slideChanging = useCallback( ({detail}) => {
    setCurLong( detail.value )
    setIsChanging(true)
  } , [song, setIsChanging, innerAudioContext])

  useEffect(()=>{

    let curId = "z" + Math.floor(curLong)

    let exist = songLyric.filter(lyric => {
      return lyric.id === curId
    }).length
    exist && setViewId(curId)
  }, [curLong, setViewId, songLyric])

  let toggleMusicList = useCallback( () => {
    setShowMusicList(pre => !pre)
  }, [setShowMusicList] )

innerAudioContext(统一管理音乐播放器的各个事件:切换歌曲)

包含了事件监听,歌词滚动,歌曲拖动等

import Taro from '@tarojs/taro'
import {Song} from "@/constants/interface"
// import { togglePlay, loadingSongDetail } from '@/actions'
import {togglePlay, loadingSongDetail} from "../store/actions"
import store from "../store"

const innerAudioContext = Taro.createInnerAudioContext()
innerAudioContext.autoplay = true


innerAudioContext.onCanplay(() => {
  let duration = innerAudioContext.duration;
  console.log('可以播放咯')
  console.log(duration)
})

export let audioTogglePlay = () => {
  let { songReducer } = store.getState()
  let {
    isPlay
  } = songReducer
  store.dispatch(togglePlay())
  innerAudioContext &&   (isPlay ? (innerAudioContext.pause() ) : (innerAudioContext.play() ))
}

export let audioToggleSong = (index = 1) => {
  let { songReducer, songListReducer, homeReducer } = store.getState()
  
  let {
    song,
  } = songReducer

  let {
    playSongListIds
  } = homeReducer

  audioTogglePlay()
  innerAudioContext.stop()

  index = isNaN(index) ? 1 : index
  let nextId
  if( index === 1 || index === -1 ){
    let songIndex = playSongListIds.indexOf(song.id) + index,
      totalLength = playSongListIds.length
    if( songIndex < 0 ){
      songIndex = songIndex + totalLength
    }else if(songIndex >= totalLength){
      songIndex = songIndex - totalLength
    }
    nextId = playSongListIds.slice(songIndex, songIndex + 1)[0]
  }else{
    nextId = index
  }
  store.dispatch(loadingSongDetail(nextId))

}


let audioOnPlay = () => {
  let duration = innerAudioContext.duration;
  console.log('开始播放')
  console.log(duration)
}

let audioOnCanplay = () => {
  let duration = innerAudioContext.duration;
  console.log('可以播放咯')
  console.log(duration)
}

export function onPlay(){

  innerAudioContext.onPlay(audioOnPlay)
  innerAudioContext.onCanplay(audioOnCanplay)
  innerAudioContext.pause()
  innerAudioContext.play()
}

export function offPlay(){
  // innerAudioContext.pause()
  innerAudioContext.offPlay( audioOnPlay )
  innerAudioContext.offCanplay( audioOnCanplay )
}



export function onEnded(){
  innerAudioContext.onEnded(audioToggleSong)
}
export function offEnded(){
  innerAudioContext.offEnded(audioToggleSong)
}

export default innerAudioContext

四、部分页面截屏

首页

歌单列表页

播放页面

搜索页面

参考

github.com/lsqy/taro-m…

总结

hooks真的非常好用,写代码感觉很舒服。