• 基于 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;
    	 100%;
    	height: 3px;
    	position: relative;
    	background: #aaa;
    	cursor: pointer;
    
    	.progress {
    		 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 {
    	 550px;
      height: 210px;
    	//margin: auto;
    	//margin-top: 0px;
    
      position: absolute;
      left: 50%;
      transform: translate(-50%, 0);
      bottom: 20px;
      z-index: 101;
      // 100%;
    
    	//.caption {
    	//	font-size: 16px;
    	//	color: rgb(47, 152, 66);
    	//}
    
    	.cover {
    		 180px;
    		height: 180px;
    		margin-left: 20px;
    
    		img {
    			 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;
        	 40px;
        }
    
        .icon {
        	cursor: pointer;
        }
    
        .ml20 {
        	margin-left: 20px;
        }
    
        .mt35 {
        	margin-top: 35px;
        }
    
        .volume-wrapper {
        	 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

  • 相关阅读:
    了解Web2.0必订阅之十大Blog[个人推荐]
    [J2ME Q&A]Target port denied to untrusted applications问题回应
    2005年Csdn十大最热门BLog作者排名第一?
    J2me流媒体技术实现讨论[1]
    液氮
    微分、差分和变分的概念
    Python mutable vs immutable (不可变对象 vs 可变对象)
    异戊烷
    免疫组化
    [导入]java escape unescape
  • 原文地址:https://www.cnblogs.com/nnngu/p/8434066.html
Copyright © 2020-2023  润新知