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

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


上一篇完成了音乐相册里面的相册功能,这一篇主要总结的是音乐相册里面的音乐播放器功能。

数据准备

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,105评论 4 62
  • 头痛欲裂,昨夜没睡好,跟五岁的你怄了一晚上的气。准确来说,我是在跟自己懊恼,不知如何爱你。 17点,下班。 17点...
    紫梦语阅读 290评论 4 2
  • 一 那啥,先讲个笑话。 孩子说,世界上有三种鸟,一种呢,是笨鸟,没关系,笨鸟就先飞。 第二种鸟嫌累,干脆就不飞。 ...
    懒妈邦阅读 824评论 0 8
  • 题记:我曾经犯过某种禁忌,却并未乐此不疲。 一 东川市,大华国的中东部地区。气候宜人,温养佳人。 “林儿,乖阿。今...
    最帅的张二明阅读 725评论 0 0