1. 程式人生 > >實戰React音樂播放器

實戰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