基于 React + Webpack 的音乐相册项目(下)

时间:2022-05-04
本文章向大家介绍基于 React + Webpack 的音乐相册项目(下),主要内容包括数据准备、进度条功能、创建播放器组件、最终效果、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

上一篇我们完成了音乐相册里面的播放图片的功能,这一篇主要完成的是音乐相册里面的音乐播放器功能。最终让我们基于 React 的音乐相册图文并茂、有声有色。

我们主要从以下几个部分来展开:

数据准备

进度条功能

创建播放器组件

最终效果

数据准备

src/data目录添加音乐数据文件:musicDatas.js

代码如下:

export const MUSIC_LIST = [
  {
    id: 1,
    title: '童话镇',
    artist: '陈一发儿',
    file: 'https://raw.githubusercontent.com/nnngu/SharedResource/master/music/%E7%AB%A5%E8%AF%9D%E9%95%87.mp3',
    cover: 'https://raw.githubusercontent.com/nnngu/FigureBed/master/2018/2/6/tong_hua_zhen.jpg'
  }, {
    id: 2,
    title: '天使中的魔鬼',
    artist: '田馥甄',
    file: 'http://oj4t8z2d5.bkt.clouddn.com/%E9%AD%94%E9%AC%BC%E4%B8%AD%E7%9A%84%E5%A4%A9%E4%BD%BF.mp3',
    cover: 'http://oj4t8z2d5.bkt.clouddn.com/%E9%AD%94%E9%AC%BC%E4%B8%AD%E7%9A%84%E5%A4%A9%E4%BD%BF.jpg'
  }, {
    id: 3,
    title: '风继续吹',
    artist: '张国荣',
    file: 'http://oj4t8z2d5.bkt.clouddn.com/%E9%A3%8E%E7%BB%A7%E7%BB%AD%E5%90%B9.mp3',
    cover: 'http://oj4t8z2d5.bkt.clouddn.com/%E9%A3%8E%E7%BB%A7%E7%BB%AD%E5%90%B9.jpg'
  }, {
    id: 4,
    title: '恋恋风尘',
    artist: '老狼',
    file: 'http://oj4t8z2d5.bkt.clouddn.com/%E6%81%8B%E6%81%8B%E9%A3%8E%E5%B0%98.mp3',
    cover: 'http://oj4t8z2d5.bkt.clouddn.com/%E6%81%8B%E6%81%8B%E9%A3%8E%E5%B0%98.jpg'
  }, {
    id: 5,
    title: '我要你',
    artist: '任素汐',
    file: 'http://oj4t8z2d5.bkt.clouddn.com/%E6%88%91%E8%A6%81%E4%BD%A0.mp3',
    cover: 'http://oj4t8z2d5.bkt.clouddn.com/%E6%88%91%E8%A6%81%E4%BD%A0.jpg'
  }, {
    id: 6,
    title: '成都',
    artist: '赵雷',
    file: 'http://oj4t8z2d5.bkt.clouddn.com/%E6%88%90%E9%83%BD.mp3',
    cover: 'http://oj4t8z2d5.bkt.clouddn.com/%E6%88%90%E9%83%BD.jpg'
  }, {
    id: 7,
    title: 'sound of silence',
    artist: 'Simon & Garfunkel',
    file: 'http://oj4t8z2d5.bkt.clouddn.com/sound-of-silence.mp3',
    cover: 'http://oj4t8z2d5.bkt.clouddn.com/sound-of-silence.jpg'
  }

];

进度条功能

1、在src/index.html文件中添加一个div,作为jPlayer(音乐播放插件)的容器。如下图红色框里面的就是新添加的代码:

2、继续在src/index.html文件中应用 jQuery 和 jPlayer 。

3、添加进度条组件:在src/components/music 目录添加 progress.js,如下图:

progress.js的代码如下:

import React from 'react';

require('./progress.less');

let Progress = React.createClass({

  getDefaultProps() {
    return {
      barColor: '#2f9842'
    }
  },

  changeProgress(e) {
    let progressBar = this.refs.progressBar;
    let progress = (e.clientX - progressBar.getBoundingClientRect().left) / progressBar.clientWidth;
    this.props.onProgressChange && this.props.onProgressChange(progress);
  },

  // ......
  
  // 省略了一部分代码
  // 完整的代码请参照项目的源代码
  
});

export default Progress;

在同一个目录下创建Progress 的样式文件 progress.less,代码如下:

.components-progress {
    display: inline-block;
    width: 100%;
    height: 3px;
    position: relative;
    background: #aaa;
    cursor: pointer;

    .progress {
        width: 0%;
        height: 3px;
        left: 0;
        top: 0;
    }
}

创建播放器组件

播放器组件分别对应player.jsplayer.less 两个文件。如下图:

player.js 的代码如下:

import React from 'react';
import Progress from './progress';
import {MUSIC_LIST} from '../../data/musicDatas';

let PubSub = require('pubsub-js');
require('./player.less');

let duration = null;

let Player = React.createClass({

  /**
   * 生命周期方法 componentDidMount
   */
  componentDidMount() {
    $('#player').bind($.jPlayer.event.timeupdate, (e) => {
      duration = e.jPlayer.status.duration;
      this.setState({
        progress: e.jPlayer.status.currentPercentAbsolute,
        volume: e.jPlayer.options.volume * 100,
        leftTime: this.formatTime(duration * (1 - e.jPlayer.status.currentPercentAbsolute / 100))
      });
    });

    $('#player').bind($.jPlayer.event.ended, (e) => {
      this.playNext();
    });
  },

  /**
   * 生命周期方法 componentWillUnmount
   */
  componentWillUnmount() {
    $('#player').unbind($.jPlayer.event.timeupdate);
  },

  formatTime(time) {
    time = Math.floor(time);
    let miniute = Math.floor(time / 60);
    let seconds = Math.floor(time % 60);

    return miniute + ':' + (seconds < 10 ? '0' + seconds : seconds);
  },

  /**
   * 进度条被拖动的处理方法
   * @param progress
   */
  changeProgressHandler(progress) {
    $('#player').jPlayer('play', duration * progress);
    this.setState({
      isPlay: true
    });

    // 获取转圈的封面图片
    let imgAnimation = this.refs.imgAnimation;
    imgAnimation.style = 'animation-play-state: running';
  },

  /**
   * 音量条被拖动的处理方法
   * @param progress
   */
  changeVolumeHandler(progress) {
    $('#player').jPlayer('volume', progress);
  },

  /**
   * 播放或者暂停方法
   **/
  play() {
    this.setState({
      isPlay: !this.state.isPlay
    });

    // 获取转圈的封面图片
    var imgAnimation = this.refs.imgAnimation;

    if (this.state.isPlay) {
      $('#player').jPlayer('pause');
      imgAnimation.style = 'animation-play-state: paused';
    } else {
      $('#player').jPlayer('play');
      imgAnimation.style = 'animation-play-state: running';
    }

  },

  /**
   * 下一首
   **/
  next() {
    PubSub.publish('PLAY_NEXT');

    this.setState({
      isPlay: true
    });

    // 开始播放下一首
    this.playNext();

    // 获取转圈的封面图片
    let imgAnimation = this.refs.imgAnimation;
    imgAnimation.style = 'animation-play-state: running';

  },

  /**
   * 上一首
   **/
  prev() {
    PubSub.publish('PLAY_PREV');

    this.setState({
      isPlay: true
    });

    // 开始播放上一首
    this.playNext('prev');

    // 获取转圈的封面图片
    let imgAnimation = this.refs.imgAnimation;
    imgAnimation.style = 'animation-play-state: running';

  },

  playNext(type = 'next') {
    let index = this.findMusicIndex(this.state.currentMusitItem);
    if (type === 'next') {
      index = (index + 1) % this.state.musicList.length;
    } else {
      index = (index + this.state.musicList.length - 1) % this.state.musicList.length;
    }
    let musicItem = this.state.musicList[index];
    this.setState({
      currentMusitItem: musicItem
    });
    this.playMusic(musicItem);
  },

  playMusic(item) {
    $('#player').jPlayer('setMedia', {
      mp3: item.file
    }).jPlayer('play');
    this.setState({
      currentMusitItem: item
    });
  },

  findMusicIndex(music) {
    let index = this.state.musicList.indexOf(music);
    return Math.max(0, index);
  },

  changeRepeat() {
    PubSub.publish('CHANAGE_REPEAT');
  },

  // constructor() {
  //   return {
  //     progress: 0,
  //     volume: 0,
  //     isPlay: true,
  //     leftTime: ''
  //   }
  // },

  componentWillMount() {
    this.getInitialState();
  },

  getInitialState() {
    return {
      musicList: MUSIC_LIST,
      currentMusitItem: MUSIC_LIST[0],
      repeatType: 'cycle',

      progress: 0,
      volume: 0,
      isPlay: true,
      leftTime: ''
    }
  },

  /**
   * render 渲染方法
   * @returns {*}
   */
  render() {
    return (
      <div className="player-page">
        <div className=" row">
          <div className="controll-wrapper">
            <h2 className="music-title">{this.state.currentMusitItem.title}</h2>
            <h3 className="music-artist mt10">{this.state.currentMusitItem.artist}</h3>
            <div className="row mt10">
              <div className="left-time -col-auto">-{this.state.leftTime}</div>
              <div className="volume-container">
                <i className="icon-volume rt" style={{top: 5, left: -5}}></i>
                <div className="volume-wrapper">

                  {/* 音量条 */}
                  <Progress
                    progress={this.state.volume}
                    onProgressChange={this.changeVolumeHandler}
                    // barColor='#aaa'
                  >
                  </Progress>
                </div>
              </div>
            </div>
            <div style={{height: 10, lineHeight: '10px'}}>

              {/* 播放进度条 */}
              <Progress
                progress={this.state.progress}
                onProgressChange={this.changeProgressHandler}
              >
              </Progress>
            </div>
            <div className="mt35 row">
              <div>
                <i className="icon prev" onClick={this.prev}></i>
                <i className={`icon ml20 ${this.state.isPlay ? 'pause' : 'play'}`} onClick={this.play}></i>
                <i className="icon next ml20" onClick={this.next}></i>
              </div>
              <div className="-col-auto">
                {/* 播放模式按钮:单曲、循环、随机 */}
                <i className={`repeat-${this.state.repeatType}`} onClick={this.changeRepeat}></i>
              </div>
            </div>
          </div>
          <div className="-col-auto cover">
            <img ref="imgAnimation" src={this.state.currentMusitItem.cover} alt={this.state.currentMusitItem.title}/>
          </div>
        </div>
      </div>
    );
  }
});

export default Player;

player.less 的代码如下:

.player-page {
    width: 550px;
  height: 210px;
    //margin: auto;
    //margin-top: 0px;

  position: absolute;
  left: 50%;
  transform: translate(-50%, 0);
  bottom: 20px;
  z-index: 101;
  //width: 100%;

    //.caption {
    //  font-size: 16px;
    //  color: rgb(47, 152, 66);
    //}

    .cover {
        width: 180px;
        height: 180px;
        margin-left: 20px;

        img {
            width: 180px;
            height: 180px;
            border-radius: 50%;
            animation: roate 20s infinite linear; // 旋转专辑封面
            border:2px solid #808080b8;
        }
    }

    .volume-container {
        position: relative;
        left: 20px;
        top: -3px;
    }

    .volume-container .volume-wrapper {
        opacity: 0;
        transition: opacity .5s linear;
    }

    .volume-container:hover .volume-wrapper {
        opacity: 1;
    }

    .music-title {
        font-size: 25px;
        font-weight: 400;
        color: rgb(3, 3, 3);
        height: 6px;
        line-height: 6px;
    }

    .music-artist {
        font-size: 15px;
        font-weight: 400;
        color: rgb(74, 74, 74);
    }

    .left-time {
        font-size: 14px;
        color: #999;
        font-weight: 400;
        width: 40px;
    }

    .icon {
        cursor: pointer;
    }

    .ml20 {
        margin-left: 20px;
    }

    .mt35 {
        margin-top: 35px;
    }

    .volume-wrapper {
        width: 60px;
        display: inline-block;
    }
}

@keyframes roate {
    0% {
        transform: rotateZ(0)
    }
    100% {
        transform: rotateZ(360deg)
    }
}

然后在src/components/Main.js中添加音乐播放器组件 Player ,完整的代码请参照我发布到 Github 上的源代码。

最终效果

到此,基于 React 的音乐相册的全部功能已经完成了。最终的运行效果如下:

源代码:https://github.com/nnngu/MusicPhoto

笔记仓库:https://github.com/nnngu/LearningNotes