对于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>
)
}
}
}
网友评论