美文网首页
【React】基于酷狗接口做的音乐播放器

【React】基于酷狗接口做的音乐播放器

作者: 北极星丶超帅的 | 来源:发表于2018-03-02 13:59 被阅读470次

对于react我没学太久就直接上手写项目,所以如果有觉得哪里有问题请尽情的指出来。
项目地址是:GitHub - Ercyao/REACT-kgmusic: 音乐播放器

基本目录结构如下:

目录结构

实现的功能

获取新歌,榜单,歌单等数据
搜索歌曲
播放完自动切歌,上一首,下一首
歌词滚动,拖动进度条改变数据
点击收藏喜欢的歌
最近听过的歌曲列表显示二十条以内的数据

页面的效果图:

推荐 分类 搜索 歌单 列表 播放页 等待播放列表 我的

开始的准备工作

建立好了react项目后,cnpm install安装基本的依赖后,有些依赖还需要手动安装,具体的根据package.json文件中的devDependencies来单独安装依赖,如:cnpm install --save-dev http-proxy-middleware

因为是请求外部数据,所以需要跨域,用node的express搭建简单的服务器。

const express = require('express');
const webpackDevMiddleware = require("webpack-dev-middleware");
const WebpackHotMiddleware = require('webpack-hot-middleware');
const webpack = require("webpack");
const proxy = require('http-proxy-middleware');
const app = express();
const config = require('./webpack.config');
config.entry.app.unshift("webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000");
const compiler = webpack(config);

app.use(webpackDevMiddleware(compiler, {
    publicPath: config.output.publicPath,
    historyApiFallback: true,
    hot: true,
    inline: true,
    noInfo: false,
    stats: {
        colors: true,
    }
}));

app.use(WebpackHotMiddleware(compiler));
const kugou = proxy('/kugou', {
    target: 'http://m.kugou.com/',
    changeOrigin: true,
    pathRewrite: {"^/kugou": ""}
});
const yy_kugou = proxy('/yy_kugou', {
    target: 'http://www.kugou.com/yy/',
    changeOrigin: true,
    pathRewrite: {"^/yy_kugou": ""}
});
const mobilecdn = proxy('/mobilecdn', {
    target: 'http://mobilecdn.kugou.com',
    changeOrigin: true,
    pathRewrite: {"^/mobilecdn": ""}
});
app.use('/kugou/*', kugou);
app.use('/yy_kugou/*', yy_kugou);
app.use('/mobilecdn/*', mobilecdn);
const server = app.listen(8888, () => {
    const host = server.address().address;
    const port = server.address().port;
    console.log('Listening at http://%s:%s', host, port);
});

因为要使音乐在任何页面都可以播放,并且不影响,在配置路由的时候,将<Audio/>放在<Switch>的外面。
router.js

import React, { Component }  from 'react';
import { HashRouter, Switch, Route, Redirect } from 'react-router-dom';
import asyncComponent from '../utils/asyncComponent';

import Home from '../pages/home/home';
const Category = asyncComponent(()=>import("../pages/category/category"));
const Search = asyncComponent(()=>import("../pages/search/search"));
const Songmenu = asyncComponent(()=>import("../pages/songmenu/songmenu"));
const Play = asyncComponent(()=>import("../pages/play/play"));
const Profile = asyncComponent(()=>import("../pages/profile/profile"));

const Ranklist = asyncComponent(()=>import("../pages/list/list"));
const Singerlist = asyncComponent(()=>import("../pages//list/list"));
const Songlist = asyncComponent(()=>import("../pages/list/list"));
const Collectlist = asyncComponent(()=>import("../pages/list/list"));

const Audio = asyncComponent(()=>import("../pages/play/Audio"));

const NotFound = asyncComponent(()=>import("../components/common/NotFound"));

export default class RouteConfig extends Component{
  render(){
    return(
      <div>
          <Audio/>
        <HashRouter>
          <Switch>
            <Route path="/" exact component={Home}/>
            <Route path="/category" exact component={Category}/>
            <Route path="/rank/list/:id" component={Ranklist}/>
            <Route path="/singer/list/:id" component={Singerlist}/>
            <Route path="/search" component={Search}/>
            <Route path="/songmenu" component={Songmenu}/>
            <Route path="/song/list/:id" component={Songlist}/>
            <Route path="/play" exact component={Play}/>
            <Route path="/profile" exact component={Profile}/>
            <Route path="/collect/list" component={Collectlist}/>
            <Route path='/404' component={NotFound} />
            <Redirect from='*' to='/404' />
          </Switch>
        </HashRouter>
      </div>
    )
  }
}

绑定audio控件需要在渲染前componentWillMount 就绑定
Audio.js

import React, { Component } from 'react';
import $ from 'jquery';
import ajax from '../../utils/Ajax';
import {parseLyric} from '../../utils/Format';
import './play.css';

export default class Audio extends Component {
    constructor(props) {
        super(props);
        this.state = {
            song:[],
        }
    }
    componentWillMount (){  
        var musicAudio = document.getElementById(musicAudio);
        var collectList = [];
    }
    componentDidMount(){
        if(!global.song){
            this.hashtimer = setInterval(() => { this.setState({song:global.song});}, 500  );
        }else{
            clearInterval(this.hashtimer);
            this.setState({song:global.song});
        }
   }
    render() {
        const currentSong = this.state.song
        return (
            <audio src={currentSong ? currentSong.play_url : null} id="musicAudio"></audio>
        )
    }
}

因为audio控件放在公共组件中,所以为了监听实时变化,使用setInterval来监听,点击暂停停止clearInterval监听,在切换歌曲时会发生需要点击两次才可以播放,所以设置var timer = setTimeout(() => {musicAudio.play();}, 500); 切歌后0.5秒后执行音乐播放。
判断是否显示收藏歌曲是全局变量global.hash时候等于global.collectList[i].hash,如果等于表示collectList里有当前播放的歌曲。
取消收藏是判断当前hash与collectList中是否有相等,相等的,根据i,删除splice(i, 1);
收藏歌曲是先存入局部变量collectList数组中,在将局部collectList和全局global.collectList进行判断,去掉局部collectList中与全局global.collectList相同的数据,再重新存入数组。

play.js

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import $ from 'jquery';
import ajax from '../../utils/Ajax';
import {parseLyric,formatTime} from '../../utils/Format';
import PublicNav from '../../components/footer/footer-nav';
import './play.css';

export default class Play extends Component {
    constructor(props) {
        super(props);
        this.state = {
            pValue:0,
            currentId:-1,
            isShowlist:false,
            isShow:false,
            isLove:false,
            song: [],
            lyrics:[],
            songList: global.songList,
            latelyList:[],
            collectList:[],
        }
        this.collectTap = this.collectTap.bind(this);
        this.hiddenSongList = this.hiddenSongList.bind(this);
        this.showSongList = this.showSongList.bind(this);
        this.playPrev = this.playPrev.bind(this);
        this.playNext = this.playNext.bind(this);
        this.playToggle = this.playToggle.bind(this);
        this.progressChange = this.progressChange.bind(this);
    }
    componentWillUnmount(){    //解决切换路由时,setState被卸载问题
        this.setState = (state,callback)=>{ //重写组件的setState方法,直接返回空
          return;
        };  
    }
    componentDidMount(){
        if(global.hash){
            this.GetMusic(global.hash);
            this.playtimer = setInterval(() => {
                this.timeUpdate();
                this.setState({currentHash:global.hash});
                if(musicAudio.currentTime == musicAudio.duration){
                    this.EndChangeSong();
                }
            }, 500  );
            this.changeCollectShow();
            var timer = setTimeout(() => {musicAudio.play();this.latelyList();}, 500);
        }else{
            musicAudio.pause()
            this.setState({
                isShow:true,
                isPlay:true
            })
        }       
    }
    //获取音乐
    GetMusic(hash){
        $.ajax({
            url: '/yy_kugou/index.php?r=play/getdata',
            data: { 
                hash: hash
            }, 
            method: 'GET',
            dataType: 'json',
            success: (data) => {
                let lyrics=parseLyric(data.data.lyrics)
                this.setState({
                    song:data.data,
                    lyrics:lyrics
                });
            },error: (error) => {new Message('warning', '获取歌曲信息失败。');}
        })
    }
    // 进度条改变触发事件
    progressChange (e) {
        const pValue =  parseFloat(e.target.value)
        const seek = pValue * (musicAudio.duration/100);
        musicAudio.currentTime=seek
        this.setState({pValue:pValue})
    }
    // 音频进度改变触发事件
    timeUpdate(){
        let pValue = musicAudio.currentTime / (musicAudio.duration/100);
        this.setState({pValue:pValue})
        this.lyrScroll()
    }
    // 歌词面板事件
    lyrScroll() {
        let lyrArry = []
        this.state.lyrics.map(function (lyr,i) {lyrArry.push(lyr[0])})
        for (var j = 0; j <= lyrArry.length; j++) {
            if (musicAudio.currentTime > lyrArry[j]) {this.setState({ currentId: j})}
        }
    }
    //点击播放or暂停
    playToggle () {
        const hash = global.hash;
        if(hash){
            if(this.state.isPlay){
                musicAudio.play()
                this.playtimer = setInterval(() => { this.timeUpdate()}, 500  );
            }else{
                musicAudio.pause()
                clearInterval(this.playtimer)
            }
            this.setState({
                isPlay:!this.state.isPlay
            })
        }
    }
    hiddenSongList(){
        this.setState({isShowlist: false});
    }
    showSongList(){
        this.setState({isShowlist: true});
    }
    //切歌
    changeSong(e){
        global.hash = e.hash;
        this.hiddenSongList();
        this.GetMusic(global.hash);
        this.changeCollectShow();
        var timer = setTimeout(() => {musicAudio.play();}, 500);  //解决换歌不能播放问题
    }
    //上一首
    playPrev() {
        const hash = global.hash;
        const songList = global.songList;
        let index = 0;
        if (songList.length > 0) {for (let i = 0; i < songList.length; i++) {if (songList[i].hash === hash) {index = i;}}}
        let currentIndex = index - 1 < 0 ? songList.length - 1 : --index;
        const currentSong = songList[currentIndex];
        global.hash = currentSong.hash
        this.GetMusic(currentSong.hash);
        this.changeCollectShow();
        this.setState({pValue:0,currentId:-1})
        if(this.state.isPlay){var timer = setTimeout(() => {musicAudio.play();}, 500);}
    }
    //下一首
    playNext() {
        const hash = global.hash;
        const songList = global.songList;
        let index = 0;
        if (songList.length > 0) {for (let i = 0; i < songList.length; i++) { if (songList[i].hash === hash) {index = i;}}}
        let currentIndex = index - 1 < 0 ? songList.length - 1 : --index;
        const currentSong = songList[currentIndex];
        global.hash = currentSong.hash
        this.GetMusic(currentSong.hash);
        this.changeCollectShow();
        this.setState({pValue:0,currentId:-1})
        if(!this.state.isPlay){var timer = setTimeout(() => {musicAudio.play();}, 500);}
    }
    //播放完自动随机切歌
    EndChangeSong (){
        let len = global.songList.length;
        let k =  Math.floor(Math.random() * len);
        global.hash = global.songList[k].hash;
        this.GetMusic(global.hash);
        this.changeCollectShow();
        let timer = setTimeout(()=> {musicAudio.play();}, 500);
    }
    //添加最近听过的歌
    latelyList () {
        let latelyList = this.state.latelyList;
        let GlobalLatelyList = global.latelyList
        if(GlobalLatelyList){
            for (var i = 0; i < 20; i++) {
                if (global.hash != global.latelyList[i].hash) {return global.latelyList.unshift(song);}else{return ;}
            }
        } else {
            latelyList.push(song);
            global.latelyList = latelyList;
        }
    }
    //点击切换收藏或取消歌曲
    collectTap() {  
        let collectList = this.state.collectList;
        let GlobalCollectList = global.collectList
        if (this.state.isLove){
            for (var i = 0; i < global.collectList.length; i++) {
              if (global.hash == global.collectList[i].hash) {
                global.collectList.splice(i, 1);
                return this.setState({ isLove: false });
              }
            }
        }else{
            this.setState({ isLove: true});
            collectList.push(song)
            //解决页面重新渲染导致,collectList又为空数组,
            if(GlobalCollectList){
                //去掉重复的
                for (var i = 0 ; i < collectList.length ; i ++ ){
                   for(var j = 0 ; j < GlobalCollectList.length ; j ++ ){
                      if (collectList[i] === GlobalCollectList[j]){collectList.splice(i,1);}
                    }
                }
                //重新存入
                for(var i = 0; i < GlobalCollectList.length; i++){collectList.push(GlobalCollectList[i]);}
                global.collectList = collectList;
            }else{global.collectList = collectList;}
        }
    }
    //判断是否是收藏了的
    changeCollectShow() {
        if (global.collectList) {
          for (var i = 0; i < global.collectList.length; i++) {
            if (global.hash == global.collectList[i].hash) {
               return this.setState({ isLove: true });
            }
          }
          return this.setState({ isLove: false });
        } else {
          this.setState({ isLove: false });
        }
    }
    render() {  
        global.song=this.state.song;
        let {isShow, isShowlist, currentHash, isLove, isPlay, currentId, pValue, song} = this.state;
        let lyrics = this.state.lyrics.map(function (lyr,i) {
            return <li key={i} className={currentId == i?'act':''}> {lyr[1]}</li>
        })
        let self = this;   //解决map添加事件报错
        let songList = [];
        if(this.state.songList){
            songList = this.state.songList.map(function (e,i) {
                return <li key={i} className={currentHash == e.hash?'red':''} onClick={self.changeSong.bind(self,e)}><span className='list-num'>{i+1}</span><span className='list-name'>{e.filename}</span></li>
            })
        }
        if (song.error) {
            return (
                <div className="container">
                   <div id='prompt_popup'><span>很抱歉,当前音乐{song.error}!</span></div>
                   <PublicNav  {...this.props} />
                </div>
            )
        } else{
         return (
            <div className="container">
              <div className='audio-box'>
                 {/* 音乐名称  */}
                <div className='audio-header'>
                   <div id='list-icon' onClick={this.showSongList}></div> 
                   <div id='love-icon' className={isLove?'love_yes':'love_no'}  onClick={this.collectTap}></div>
                   <div className='songname'> {song.song_name}</div> 
                   <div className='singername'>——<span className='sn'>{song.author_name}</span>——</div>
                </div>
                 {/* 歌词面板  */}
                <div className='lyrics-box'>
                  <ul className='lyrics-panels' style={{transform:'translateY(-' + currentId * 30 + 'px)'}}>{lyrics}</ul>
                </div>
                 {/* 进度  */}
                <div className='time-box'><span>{formatTime(musicAudio.currentTime)}</span><span>{formatTime(musicAudio.duration)}</span></div>
                <div className='progress-bar'>
                    <span className="progress-change" style={{width: pValue + '%'}}></span>
                    <input type='range' step='0.01' max={100} min={0} value={pValue || '0'}  onChange={this.progressChange} className="progress-range"/>
                </div>
                 {/* 控制音乐按钮  */}
                <div className='player-btn'>
                  <div className='player-item cut' onClick={this.playPrev}><div className='upcut'></div></div>
                  <div className='player-item control' onClick={this.playToggle}><div className={isPlay?'play':'pause'}></div></div>
                  <div className='player-item cut' onClick={this.playNext}><div className='downcut'></div></div>
                </div>
                {/* 播放列表  */}
                <div id='player-list' className={isShowlist?'block':'none'}>
                  <div className='list-bg' onClick={this.hiddenSongList}></div>
                  <div className='list-con'>
                    <div className="list-title">等待播放列表<span className="list-close" onClick={this.hiddenSongList}>X</span></div >
                    <ul>{songList}</ul>
                  </div >
               </div>
                {/* 提示弹窗  */}
                <div id='prompt_popup' className={isShow?'block':'none'}><span>当前无音乐...</span></div>
             </div>
              <PublicNav  {...this.props} />
            </div>         
          )
        }
    }
}

通过地址栏,判断获取哪个列表数据并渲染。
列表list.js

import React, { Component } from 'react';
import {Link} from 'react-router-dom';
import $ from 'jquery';
import ajax from '../../utils/Ajax';
import PublicGoback from '../../components/header/header-goback';
import {formatTime} from '../../utils/Format';
import './list.css';

export default class Ranklist extends Component {
    constructor(props) {
        super(props);
        this.state = {
            Rankinfo: [],
            Ranklist: [],
            Songlist: [],
            SonglistInfo:[],
            Singerlist: [],
            Collectlist:[],
            list:'',
        }
   }
    componentDidMount(e){
        if(this.props.location.state.rankId){
            let rankid  = this.props.location.state.rankId
            $.ajax({
                url: '/kugou/rank/info',
                data: { 
                    rankid:rankid,
                    json:true
                }, 
                method: 'GET',
                dataType: 'json',
                success: (data) => {
                    this.setState({
                        Rankinfo:data.info,
                        Ranklist:data.songs.list
                    });
                },error: (error) => {console.log('未加载到数据');}
            })  
            this.setState({list:1});
        }else if(this.props.location.state.specialId){
            let specialid = this.props.location.state.specialId
            $.ajax({
                url: '/kugou/plist/list',
                data: { 
                    specialid:specialid,
                    json:true
                }, 
                method: 'GET',
                dataType: 'json',
                success: (data) => {
                    this.setState({
                        SonglistInfo:data.info.list,
                        Songlist:data.list.list.info
                    });
                },error: (error) => {console.log('未加载到数据');}
            })
            this.setState({list:2});
        }else if(this.props.location.state.classId){
            let classid = this.props.location.state.classId
            $.ajax({
                url: '/kugou/singer/list',
                data: { 
                    classid:classid,
                    json:true
                }, 
                method: 'GET',
                dataType: 'json',
                success: (data) => {
                    this.setState({
                        Singerlist:data.singers.list.info
                    });
                },error: (error) => {console.log('未加载到数据');}
            })
            this.setState({list:3});
        }else if(this.props.location.state.love){
            this.setState({
                Collectlist:global.collectList,
                list:4
          });
        }
    }
    onPlayTap(e){
        global.hash = e.hash;
        if(this.state.list == 1){
            global.songList = this.state.Ranklist;
        }else if(this.state.list == 2){
            global.songList = this.state.Songlist;
        }else{
            global.songList = this.state.Collectlist;
        }
    }
    render() {
        if(this.state.list == 1){
            let Rankinfo = this.state.Rankinfo
            let Ranklist = this.state.Ranklist.map(function (e,i) {
            return  <Link to={'/play'} onClick={this.onPlayTap.bind(this,e)} key={i}>
                        <li><span className="index">{i+1}</span><span className="song name">{e.filename}</span><span>{formatTime(e.duration)}</span></li>
                    </Link>
            },this)
            return (
                <div className="list-wrapper">
                  <PublicGoback  title={Rankinfo.rankname} subhead="那一天  闭目在经殿的香雾中  蓦然听见  你诵经的真言"/>
                  <ul className="list-box">{Ranklist}</ul> 
                </div>         
            )
        }else if(this.state.list == 2){
            let SonglistInfo =  this.state.SonglistInfo
            let Songlist = this.state.Songlist.map(function (e,i) {
            return  <Link to={'/play'} onClick={this.onPlayTap.bind(this,e)} key={i}>
                       <li><span className="index">{i+1}</span><span className="song name">{e.filename}</span><span>{formatTime(e.duration)}</span></li>
                    </Link>
            },this)
            return (
                <div className="list-wrapper">
                <PublicGoback  title={SonglistInfo.specialname} name={SonglistInfo.nickname} subhead={SonglistInfo.intro}/>
                  <ul className="list-box">{Songlist}</ul> 
                </div>         
            )
        }else if(this.state.list == 3){
            let Singerlist = this.state.Singerlist.map(function (m,i) {
                return <li key={i}><span className="index">{i+1}</span><span className="singer name">{m.singername}</span></li>
            })
            return (
                <div className="list-wrapper">
                  <PublicGoback  title="歌手" subhead="那一月  我转动所有的经筒  不为超度  只为触摸你的指尖  "/>
                  <ul className="list-box">{Singerlist}</ul> 
                </div>         
            )
        }else if(this.state.list == 4){
            let Collectlist = this.state.Collectlist.map(function (e,i) {
            return  <Link to={'/play'} onClick={this.onPlayTap.bind(this,e)} key={i}>
                       <li><span className="index">{i+1}</span><span className="singer name">{e.audio_name}</span></li>
                    </Link>
            },this)
            return (
                <div className="list-wrapper">
                  <PublicGoback  title="收藏的歌曲" name="听喜欢的歌" subhead="那一世  我转山转水转佛塔呀  不为修来世  只为在途中与你相见  "/>
                  <ul className="list-box">{Collectlist}</ul> 
                </div>         
            )
        }else{
             return (
                <div className="list-wrapper">
                 <PublicGoback  title="报错" name="未加载的数据..." subhead="请返回刷新"/>
                </div>         
            )
        }
        
        
    }
}

具体的项目GitHub - Ercyao/REACT-kgmusic: 音乐播放器

相关文章

网友评论

      本文标题:【React】基于酷狗接口做的音乐播放器

      本文链接:https://www.haomeiwen.com/subject/chaoxftx.html