实战React音乐播放器

Posted 王乐平

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实战React音乐播放器相关的知识,希望对你有一定的参考价值。

上篇文章《一步一步实战HTML音乐播放器》中,我用html+JS + CSS的方式一步步实现了一个音乐播放器,因为最近接触了一下React,感觉挺不错的,在这里我用React的方式实现一个同样的音乐播放器。


播放器功能

  • 自动显示 专辑图片、歌手名、歌曲名、专辑名
  • 显示播放器进度条
  • 音乐播放暂停、上一曲、下一曲
  • 实时显示播放时间、播放总长度
  • 歌曲播放完后,自动切换下一曲

播放器效果


React 环境准备

在这个小项目中,不再使用传统的构建React的方式来搭建环境了,这里用一种很方便的小工具来实现环境的搭建。

在Node.js环境下执行如下命令,安装一下create-react-app,并创建musicPlayer项目:

npm install -g create-react-app
create-react-app musicPlayer
cd musicPlayer
npm start

执行完后后,工具会自动打开浏览器来显示这个项目的内容,效果如下:

这里我用到的是src目录,首先把src目录的内容全部删除,我们一点一点的来编写项目代码。


引入必要文件

因为系统默认将index.js作为入口文件,所以我们要先在src下创建index.js文件,所有代码也是在这个文件中编写。

先在index.js中引入一些必要的文件:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.min.css';

index.min.css是播放器的样式文件,这里主要说React,这个样式文件的详细说明可以参考 《一步一步实战HTML音乐播放器》


创建播放器容器组件

var Player = React.createClass({

    render: function() {        
        return (
            <div className="player">
                {/* 各类子组建……  */}
            </div>
        );
    }

});

子组建我们下面会一一创建,这里先用做一下占位说明。


页面渲染

ReactDOM.render(
    <Player />,
    document.getElementById('root')
);

容器内的各类组件

根据播放器结构,创建如下组件:

var Player = React.createClass({

    render: function() {        
        return (
            <div className="player">
                {/* 播放器名称  */}
                <div className="header">音乐播放器.React版</div>              

                {/* 音乐信息  */}
                <TrackInfo />

                {/* 播放进度条   */}
                <Progress />

                {/* 播放控制  */}
                <Controls />

                {/* 播放时间   */}
                <Time />

                {/* 音频控件  */}
                <audio id="audio"></audio>
            </div>
        );
    }

});

初始化STATE,PROPS

根据需求,进行状态和属性的创建。

这里歌单就手动制作一个了,把这个歌单写的props中,用于系统的调用:

    getDefaultProps: function() {
        //歌单列表
        return{
            "tracks": [
            {
                "name": "元日",
                "artists": [
                    {
                        "name": "于文华",
                    }
                ],
                "album": {
                    "name": "国学唱歌集",
                    "picUrl": "http://p3.music.126.net/SR9eFEjRB0NsscxN7-fHMw==/3344714372906000.jpg",                    
                },
                "duration": 136829,
                "mp3Url": "http://m2.music.126.net/rUcfqqZbq7TIfJeAHfTrkw==/3376600210116829.mp3"
            },
            {
                "name": "元日 ",
                "artists": [
                    {
                        "name": "清弄",
                    }
                ],
                "album": {
                    "name": "热门华语261",
                    "picUrl": "http://p4.music.126.net/ly2FJHh5-lYMdC3NZxvavQ==/7714173580661848.jpg",
                },
                "duration": 109000,
                "mp3Url": "http://m2.music.126.net/jwwZVlWJ78HEarft42uKUQ==/7906588115920636.mp3"
            },
            {
                "name": "青龙·花木苍苍",
                "artists": [
                    {
                        "name": "五色石南叶",
                    }
                ],
                "album": {
                    "name": "热门华语234",
                    "picUrl": "http://p4.music.126.net/tHAfnugCElS93EDp5cHLIw==/8909342719897560.jpg",
                },
                "duration": 295575,
                "mp3Url": "http://m2.music.126.net/rnq_W32zFX_utQbBhE0xkg==/8934631487358481.mp3"
            }]
        }   
    },

接着初始化一下播放器的状态:

    //初始化状态
    getInitialState: function() {
        return{
            currentTrackLen: this.props.tracks.length, //歌单歌曲数
            currentTrackIndex: 0, //当前播放的歌曲索引,默认加载第一首歌
            currentTime: 0, //当前歌曲播放的时间
            currentTotalTime: 0, //当前歌曲的总时间
            playStatus: true, //true为播放状态,false为暂停状态        
        }
    },

创建子组件

TrackInfo组件

var TrackInfo = React.createClass({
    render: function() {
        return(
        <div>
        <div className="albumPic" style={{'backgroundImage':'url('+ this.props.track.album.picUrl +')'}}></div>
        <div className='trackInfo'>
            <div className="name">{this.props.track.name}</div>
            <div className="artist">{this.props.track.artists[0].name}</div>
            <div className="album">{this.props.track.album.name}</div>          
        </div>
        </div>
        );
    }
});

Player容器中的标签修改为:

{/* 音乐专辑  */}
<TrackInfo track={this.props.tracks[this.state.currentTrackIndex]} />

Progress组件

var Progress = React.createClass({
    render: function(){
        return  (
            <div className="progress" style={{'width':this.props.progress}}></div>
        )
    }
});

Player容器中的标签修改为:

{/* 播放进度条   */}
<Progress progress={this.state.currentTime / this.state.currentTotalTime * 100 + '%'} />

通过当前时间和总时间来计算播放百分百。

Controls组件

var Controls = React.createClass({
    render: function(){
        let className;
        if(this.props.isPlay == true){
            className = 'icon-pause';
        }else{
            className = 'icon-play';
        }
        return (
        <div className="controls">
            <div className="play" onClick={this.props.onPlay}>
                <i className={className}></i>
            </div>
            <div className="previous" onClick={this.props.onPrevious}>
                <i className="icon-previous"></i>
            </div>
            <div className="next" onClick={this.props.onNext}>
                <i className="icon-next"></i>
            </div>
        </div>              
        )
    }
});

通过isPlay来控制播放按钮图标的显示。

Player容器中的标签修改为:

{/* 播放控制  */}
<Controls isPlay={this.state.playStatus} onPlay={this.play} onPrevious={this.previous} onNext={this.next} />

Time组件

var Time = React.createClass({
    timeConvert: function(timestamp){
        var minutes = Math.floor(timestamp / 60);
        var seconds = Math.floor(timestamp - (minutes * 60));

        if(seconds < 10) {
          seconds = '0' + seconds;
        }

        timestamp = minutes + ':' + seconds;
        return timestamp;
    },  
    render:function() {
        return(
        <div className="time">
            <div className="current">{this.timeConvert(this.props.currentTime)}</div>
            <div className="total">{this.timeConvert(this.props.currentTotalTime)}</div>
        </div>          
        );
    }
});

timeConvert做为一个时间转换显示来用。

Player容器中的标签修改为:

{/* 播放时间   */}
<Time currentTime={this.state.currentTime} currentTotalTime={this.state.currentTotalTime} />

audio标签

audio这里不需要在创建组件了,修改一下在Player中的标记就行:

{/* 音频控件  */}
<audio id="audio" src={this.props.tracks[this.state.currentTrackIndex].mp3Url}></audio>

事件处理方法

创建updatePlayStatus方法用于更新播放器的状态:

    //更新播放状态
    updatePlayStatus: function(){
        let audio = document.getElementById('audio');
        if(this.state.playStatus){
            audio.play();
        }else{
            audio.pause();
        }

        //更新当前歌曲总时间
        this.setState({currentTotalTime: this.props.tracks[this.state.currentTrackIndex].duration / 1000});
    },

创建三个播放控制按钮的事件方法:

    //播放事件处理
    play:function(){
        //这里有setState是异步的,需要在回调中执行
        this.setState({playStatus:!this.state.playStatus}, ()=>{
            this.updatePlayStatus();
        });
    },

    //上一曲事件处理
    previous:function(){
        if(this.state.currentTrackIndex - 1 < 0){
            alert('已经没有上一首了');
        }else{
            this.setState({currentTrackIndex:--this.state.currentTrackIndex},()=>{
                this.updatePlayStatus();
            });
        }        
    },

    //下一曲事件处理
    next:function(){
        if(this.state.currentTrackIndex + 1 >=  this.state.currentTrackLen){
            alert('已经没有下一首了');
        }else{
            this.setState({currentTrackIndex:++this.state.currentTrackIndex},()=>{
                this.updatePlayStatus();
            });            
        }
    },

在页面渲染完成后需要执行一下updatePlayStatus方法,根据React生命周期,我们在DOM加载完成后执行一下这个方法:

    componentDidMount: function(){      
        this.updatePlayStatus();
    },

好了,各类事件的方法基本完成,这里还需要一个监测的方法,用来实时更新播放时间和自动下一曲:

    componentDidMount: function(){      
        this.updatePlayStatus();
        setInterval(()=>{
            let audio = document.getElementById('audio');
            this.setState({currentTime:audio.currentTime},()=>{
                if(~~this.state.currentTime >= ~~this.state.currentTotalTime){
                    this.next();
                }
            });
        }, 300);
    },

完整代码

//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.min.css';

var Player = React.createClass({
    getDefaultProps: function() {
        //歌单列表
        return{
            "tracks": [
            {
                "name": "元日",
                "artists": [
                    {
                        "name": "于文华",
                    }
                ],
                "album": {
                    "name": "国学唱歌集",
                    "picUrl": "http://p3.music.126.net/SR9eFEjRB0NsscxN7-fHMw==/3344714372906000.jpg",                    
                },
                "duration": 136829,
                "mp3Url": "http://m2.music.126.net/rUcfqqZbq7TIfJeAHfTrkw==/3376600210116829.mp3"
            },
            {
                "name": "元日 ",
                "artists": [
                    {
                        "name": "清弄",
                    }
                ],
                "album": {
                    "name": "热门华语261",
                    "picUrl": "http://p4.music.126.net/ly2FJHh5-lYMdC3NZxvavQ==/7714173580661848.jpg",
                },
                "duration": 109000,
                "mp3Url": "http://m2.music.126.net/jwwZVlWJ78HEarft42uKUQ==/7906588115920636.mp3"
            },
            {
                "name": "青龙·花木苍苍",
                "artists": [
                    {
                        "name": "五色石南叶",
                    }
                ],
                "album": {
                    "name": "热门华语234",
                    "picUrl": "http://p4.music.126.net/tHAfnugCElS93EDp5cHLIw==/8909342719897560.jpg",
                },
                "duration": 295575,
                "mp3Url": "http://m2.music.126.net/rnq_W32zFX_utQbBhE0xkg==/8934631487358481.mp3"
            }]
        }   
    },

    //初始化状态
    getInitialState: function() {
        return{
            currentTrackLen: this.props.tracks.length, //歌单歌曲数
            currentTrackIndex: 0, //当前播放的歌曲索引,默认加载第一首歌
            currentTime: 0, //当前歌曲播放的时间
            currentTotalTime: 0, //当前歌曲的总时间
            playStatus: true, //true为播放状态,false为暂停状态        
        }
    },

    //更新播放状态
    updatePlayStatus: function(){
        let audio = document.getElementById('audio');
        if(this.state.playStatus){
            audio.play();
        }else{
            audio.pause();
        }

        //更新当前歌曲总时间
        this.setState({currentTotalTime: this.props.tracks[this.state.currentTrackIndex].duration / 1000});
    },

    //播放事件处理
    play:function(){
        //这里有setState是异步的,需要在回调中执行
        this.setState({playStatus:!this.state.playStatus}, ()=>{
            this.updatePlayStatus();
        });
    },

    //上一曲事件处理
    previous:function(){
        if(this.state.currentTrackIndex - 1 < 0){
            alert('已经没有上一首了');
        }else{
            this.setState({currentTrackIndex:--this.state.currentTrackIndex},()=>{
                this.updatePlayStatus();
            });
        }        
    },

    //下一曲事件处理
    next:function(){
        if(this.state.currentTrackIndex + 1 >=  this.state.currentTrackLen){
            alert('已经没有下一首了');
        }else{
            this.setState({currentTrackIndex:++this.state.currentTrackIndex},()=>{
                this.updatePlayStatus();
            });            
        }
    },

    //DOM加载完
    componentDidMount: function(){      
        this.updatePlayStatus();
        setInterval(()=>{
            let audio = document.getElementById('audio');
            this.setState({currentTime:audio.currentTime},()=>{
                if(~~this.state.currentTime >= ~~this.state.currentTotalTime){
                    this.next();
                }
            });
        }, 300);
    },
    render: function() {        
        return (
            <div className="player">
                {/* 播放器名称  */}
                <div className="header">音乐播放器.React版</div>              

                {/* 音乐信息  */}
                <TrackInfo track={this.props.tracks[this.state.currentTrackIndex]} />

                {/* 播放进度条   */}
                <Progress progress={this.state.currentTime / this.state.currentTotalTime * 100 + '%'} />

                {/* 播放控制  */}
                <Controls isPlay={this.state.playStatus} onPlay={this.play} onPrevious={this.previous} onNext={this.next} />

                {/* 播放时间   */}
                <Time currentTime={this.state.currentTime} currentTotalTime={this.state.currentTotalTime} />

                {/* 音频控件  */}
                <audio id="audio" src={this.props.tracks[this.state.currentTrackIndex].mp3Url}></audio>
            </div>
        );
    }
});

var TrackInfo = React.createClass({
    render: function() {
        return(
        <div>
        <div className="albumPic" style={{'backgroundImage':'url('+ this.props.track.album.picUrl +')'}}></div>
        <div className='trackInfo'>
            <div className="name">{this.props.track.name}</div>
            <div className="artist">{this.props.track.artists[0].name}</div>
            <div className="album">{this.props.track.album.name}</div>          
        </div>
        </div>
        );
    }
});

var Progress = React.createClass({
    render: function(){
        return  (
            <div className="progress" style={{'width':this.props.progress}}></div>
        )
    }
});

var Controls = React.createClass({
    render: function(){
        let className;
        if(this.props.isPlay == true){
            className = 'icon-pause';
        }else{
            className = 'icon-play';
        }
        return (
        <div className="controls">
            <div className="play" onClick={this.props.onPlay}>
                <i className={className}></i>
            </div>
            <div className="previous" onClick={this.props.onPrevious}>
                <i className="icon-previous"></i>
            </div>
            <div className="next" onClick={this.props.onNext}>
                <i className="icon-next"></i>
            </div>
        </div>              
        )
    }
});

var Time = React.createClass({
    timeConvert: function(timestamp){
        var minutes = Math.floor(timestamp / 60);
        var seconds = Math.floor(timestamp - (minutes * 60));

        if(seconds < 10) {
          seconds = '0' + seconds;
        }

        timestamp = minutes + ':' + seconds;
        return timestamp;
    },  
    render:function() {
        return(
        <div className="time">
            <div className="current">{this.timeConvert(this.props.currentTime)}</div>
            <div className="total">{this.timeConvert(this.props.currentTotalTime)}</div>
        </div>          
        );
    }
});

ReactDOM.render(
    <Player />,
    document.getElementById('root')
);

发布项目

在Node.js环境下执行:

npm run build

进行代码打包处理,打包文件生成了项目目录下的build中,执行如下命令,可直接查看build打包后的内容:

npm install -g pushstate-server
pushstate-server build

浏览器中输入如下地址:http://localhost:9000


好了,用React来实现音乐播放器彻底完成,因为React我也是刚接触不久,代码中可能存在着缺点,我这里就抛砖引玉了。

总体来说,用React的思想来做东西,确实挺好的,逻辑上也变得比较清晰。

React 音乐播放器代码下载:代码下载


博客名称:王乐平博客

博客地址:http://blog.lepingde.com

CSDN博客地址:http://blog.csdn.net/lecepin

以上是关于实战React音乐播放器的主要内容,如果未能解决你的问题,请参考以下文章

08.ajax音乐播放器实战

音乐播放制作(html css js)

如何在 React Native Expo 中播放背景音乐?

蓝懿IOS实战音乐播放器

百度云网盘-08.ajax音乐播放器实战

百度云课程:ajax音乐播放器实战