├── .eslintrc ├── .gitignore ├── .npmignore ├── .stylelintrc ├── LICENSE ├── README.md ├── dist ├── MusicPlayer.js ├── MusicPlayer.scss └── components │ └── Progress │ ├── index.js │ └── index.scss ├── package.json ├── public ├── index.html └── manifest.json ├── src ├── MusicPlayer.js ├── MusicPlayer.scss ├── components │ └── Progress │ │ ├── index.js │ │ └── index.scss ├── examples │ ├── BasicPlayer │ │ └── index.js │ ├── ExampleComponent.js │ ├── VerticalPlayer │ │ └── index.js │ └── playlist.js └── index.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": ["airbnb", "prettier"], 4 | "plugins": ["react", "jsx-a11y", "import"], 5 | "rules": { 6 | "react/forbid-prop-types": "off", 7 | "react/prefer-stateless-function": "off", 8 | "react/prop-types": "off", 9 | "react/no-danger": "off", 10 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 11 | "react/jsx-one-expression-per-line": "off", 12 | "react/jsx-wrap-multilines": "off", 13 | "linebreak-style": "off", 14 | "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], 15 | "no-shadow": "off", 16 | "jsx-a11y/anchor-is-valid": "off" 17 | }, 18 | "settings": { 19 | "import/core-modules": [] 20 | }, 21 | "env": { 22 | "browser": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # css 24 | src/**/*.css 25 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | public 4 | src 5 | .eslintrc 6 | .stylelintrc 7 | .gitignore -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-recommended-scss", 3 | "rules": { 4 | "indentation": 2 5 | } 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Shixiang Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Responsive Music Player 2 | 3 | ## Example 4 | 5 | Please see a [demo](http://alickzhang.github.io/react-responsive-music-player/). 6 | 7 | ![](http://res.cloudinary.com/alick/image/upload/v1502761479/screenshot_mem5hg.png) 8 | ![](http://res.cloudinary.com/alick/image/upload/v1502763028/screenshot_uhqb7f.png) 9 | 10 | ## Install 11 | 12 | ```bash 13 | npm install react-responsive-music-player --save 14 | ``` 15 | 16 | or 17 | 18 | ```bash 19 | yarn add react-responsive-music-player 20 | ``` 21 | 22 | ## Usage 23 | 24 | ```jsx 25 | import React, { Component } from 'react'; 26 | import MusicPlayer from 'react-responsive-music-player'; 27 | 28 | class App extends Component { 29 | render() { 30 | return ( 31 |
32 | 33 |
34 | ); 35 | } 36 | } 37 | ``` 38 | 39 | ## API 40 | 41 | ### props 42 | 43 | | prop | type | default | notes | 44 | | ------------- | -------------- | ---------- | ------------------------------------------------ | 45 | | mode | string | horizontal | set the layout of player: vertical or horizontal | 46 | | width | number, string | 100% | 47 | | autoplay | bool | false | 48 | | progressColor | string | #66cccc | the color of the progress | 49 | | btnColor | string | #4a4a4a | the color of the buttons | 50 | | playlist | array | [] | the playlist | 51 | | style | object | {} | 52 | 53 | ### JSON 54 | 55 | ``` 56 | const playlist = [ 57 | { 58 | url: 'path/to/mp3', 59 | cover: 'path/to/jpg', 60 | title: 'Despacito', 61 | artist: [ 62 | 'Luis Fonsi', 63 | 'Daddy Yankee' 64 | ] 65 | }, 66 | { 67 | url: 'path/to/mp3', 68 | cover: 'path/to/jpg', 69 | title: 'Bedtime Stories', 70 | artist: [ 71 | 'Jay Chou' 72 | ] 73 | } 74 | ] 75 | ``` 76 | 77 | ## Development 78 | 79 | ```bash 80 | npm install 81 | npm start 82 | ``` 83 | 84 | ## License 85 | 86 | MIT License 87 | 88 | Copyright (c) 2019 Shixiang Zhang 89 | -------------------------------------------------------------------------------- /dist/MusicPlayer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | var _react = _interopRequireWildcard(require("react")); 9 | 10 | var _propTypes = _interopRequireDefault(require("prop-types")); 11 | 12 | var _classnames = _interopRequireDefault(require("classnames")); 13 | 14 | var _Progress = _interopRequireDefault(require("./components/Progress")); 15 | 16 | require("./MusicPlayer.scss"); 17 | 18 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 19 | 20 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } 21 | 22 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 23 | 24 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } 25 | 26 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 27 | 28 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 29 | 30 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 31 | 32 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 33 | 34 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 35 | 36 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 37 | 38 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 39 | 40 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 41 | 42 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 43 | 44 | var formatTime = function formatTime(time) { 45 | /* eslint no-restricted-globals: off */ 46 | if (isNaN(time) || time === 0) { 47 | return ''; 48 | } 49 | 50 | var mins = Math.floor(time / 60); 51 | var secs = (time % 60).toFixed(); 52 | return "".concat(mins < 10 ? '0' : '').concat(mins, ":").concat(secs < 10 ? '0' : '').concat(secs); 53 | }; 54 | 55 | var processArtistName = function processArtistName(artistList) { 56 | return artistList.join(' / '); 57 | }; 58 | 59 | var getPlayModeClass = function getPlayModeClass(playMode) { 60 | if (playMode === 'loop') return 'refresh'; 61 | if (playMode === 'random') return 'random'; 62 | return 'repeat'; 63 | }; 64 | 65 | var MusicPlayer = 66 | /*#__PURE__*/ 67 | function (_Component) { 68 | _inherits(MusicPlayer, _Component); 69 | 70 | function MusicPlayer(props) { 71 | var _this; 72 | 73 | _classCallCheck(this, MusicPlayer); 74 | 75 | _this = _possibleConstructorReturn(this, _getPrototypeOf(MusicPlayer).call(this, props)); 76 | 77 | _defineProperty(_assertThisInitialized(_this), "updateProgress", function () { 78 | var _this$audioContainer$ = _this.audioContainer.current, 79 | duration = _this$audioContainer$.duration, 80 | currentTime = _this$audioContainer$.currentTime; 81 | var progress = currentTime / duration || 0; 82 | 83 | _this.setState({ 84 | progress: progress, 85 | leftTime: duration - currentTime 86 | }); 87 | }); 88 | 89 | _defineProperty(_assertThisInitialized(_this), "end", function () { 90 | _this.handleNext(); 91 | }); 92 | 93 | _defineProperty(_assertThisInitialized(_this), "handleAdjustProgress", function (value) { 94 | var currentTime = _this.audioContainer.current.duration * value; 95 | _this.audioContainer.current.currentTime = currentTime; 96 | 97 | _this.setState({ 98 | play: true, 99 | progress: value 100 | }, function () { 101 | return _this.audioContainer.current.play(); 102 | }); 103 | }); 104 | 105 | _defineProperty(_assertThisInitialized(_this), "handleAdjustVolume", function (value) { 106 | var volume = value < 0 ? 0 : value; 107 | _this.audioContainer.current.volume = volume; 108 | 109 | _this.setState({ 110 | volume: volume 111 | }); 112 | }); 113 | 114 | _defineProperty(_assertThisInitialized(_this), "handleToggle", function () { 115 | var play = _this.state.play; 116 | 117 | if (play) { 118 | _this.audioContainer.current.pause(); 119 | } else { 120 | _this.audioContainer.current.play(); 121 | } 122 | 123 | _this.setState(function (_ref) { 124 | var play = _ref.play; 125 | return { 126 | play: !play 127 | }; 128 | }); 129 | }); 130 | 131 | _defineProperty(_assertThisInitialized(_this), "handlePrev", function () { 132 | var playlist = _this.props.playlist; 133 | var _this$state = _this.state, 134 | playMode = _this$state.playMode, 135 | activeMusicIndex = _this$state.activeMusicIndex; 136 | 137 | if (playMode === 'repeat') { 138 | _this.playMusic(activeMusicIndex); 139 | } else if (playMode === 'loop') { 140 | var total = playlist.length; 141 | var index = activeMusicIndex > 0 ? activeMusicIndex - 1 : total - 1; 142 | 143 | _this.playMusic(index); 144 | } else if (playMode === 'random') { 145 | var randomIndex = Math.floor(Math.random() * playlist.length); 146 | 147 | while (randomIndex === activeMusicIndex) { 148 | randomIndex = Math.floor(Math.random() * playlist.length); 149 | } 150 | 151 | _this.playMusic(randomIndex); 152 | } else { 153 | _this.setState({ 154 | play: false 155 | }); 156 | } 157 | }); 158 | 159 | _defineProperty(_assertThisInitialized(_this), "handleNext", function () { 160 | var playlist = _this.props.playlist; 161 | var _this$state2 = _this.state, 162 | playMode = _this$state2.playMode, 163 | activeMusicIndex = _this$state2.activeMusicIndex; 164 | 165 | if (playMode === 'repeat') { 166 | _this.playMusic(activeMusicIndex); 167 | } else if (playMode === 'loop') { 168 | var total = playlist.length; 169 | var index = activeMusicIndex < total - 1 ? activeMusicIndex + 1 : 0; 170 | 171 | _this.playMusic(index); 172 | } else if (playMode === 'random') { 173 | var randomIndex = Math.floor(Math.random() * playlist.length); 174 | 175 | while (randomIndex === activeMusicIndex) { 176 | randomIndex = Math.floor(Math.random() * playlist.length); 177 | } 178 | 179 | _this.playMusic(randomIndex); 180 | } else { 181 | _this.setState({ 182 | play: false 183 | }); 184 | } 185 | }); 186 | 187 | _defineProperty(_assertThisInitialized(_this), "handleChangePlayMode", function () { 188 | var playMode = _this.state.playMode; 189 | 190 | var index = _this.modeList.indexOf(playMode); 191 | 192 | index = (index + 1) % _this.modeList.length; 193 | 194 | _this.setState({ 195 | playMode: _this.modeList[index] 196 | }); 197 | }); 198 | 199 | _defineProperty(_assertThisInitialized(_this), "playMusic", function (index) { 200 | _this.setState({ 201 | activeMusicIndex: index, 202 | leftTime: 0, 203 | play: true, 204 | progress: 0 205 | }, function () { 206 | _this.audioContainer.current.currentTime = 0; 207 | 208 | _this.audioContainer.current.play(); 209 | }); 210 | }); 211 | 212 | _this.state = { 213 | activeMusicIndex: 0, 214 | leftTime: 0, 215 | play: props.autoplay || false, 216 | playMode: 'loop', 217 | progress: 0, 218 | volume: 1 219 | }; 220 | _this.modeList = ['loop', 'random', 'repeat']; 221 | _this.audioContainer = _react.default.createRef(); 222 | return _this; 223 | } 224 | 225 | _createClass(MusicPlayer, [{ 226 | key: "componentDidMount", 227 | value: function componentDidMount() { 228 | this.audioContainer.current.addEventListener('timeupdate', this.updateProgress); 229 | this.audioContainer.current.addEventListener('ended', this.end); 230 | } 231 | }, { 232 | key: "componentWillUnmount", 233 | value: function componentWillUnmount() { 234 | this.audioContainer.current.removeEventListener('timeupdate', this.updateProgress); 235 | this.audioContainer.current.removeEventListener('ended', this.end); 236 | } 237 | }, { 238 | key: "render", 239 | value: function render() { 240 | var _this$props = this.props, 241 | playlist = _this$props.playlist, 242 | mode = _this$props.mode, 243 | width = _this$props.width, 244 | progressColor = _this$props.progressColor, 245 | btnColor = _this$props.btnColor, 246 | style = _this$props.style; 247 | var _this$state3 = this.state, 248 | play = _this$state3.play, 249 | progress = _this$state3.progress, 250 | leftTime = _this$state3.leftTime, 251 | volume = _this$state3.volume, 252 | activeMusicIndex = _this$state3.activeMusicIndex, 253 | playMode = _this$state3.playMode; 254 | var activeMusic = playlist[activeMusicIndex]; 255 | var playModeClass = getPlayModeClass(playMode); 256 | var btnStyle = { 257 | color: btnColor 258 | }; 259 | return _react.default.createElement("div", { 260 | className: (0, _classnames.default)('player', { 261 | vertical: mode === 'vertical' 262 | }), 263 | style: _objectSpread({}, style, { 264 | width: typeof width === 'string' ? width : "".concat(width, "px") 265 | }) 266 | }, _react.default.createElement("audio", { 267 | autoPlay: play, 268 | preload: "auto", 269 | ref: this.audioContainer, 270 | src: activeMusic.url 271 | }, _react.default.createElement("track", { 272 | kind: "captions" 273 | })), _react.default.createElement("div", { 274 | className: "player-control" 275 | }, _react.default.createElement("div", { 276 | className: "music-info" 277 | }, _react.default.createElement("h2", { 278 | className: "title" 279 | }, activeMusic.title), _react.default.createElement("h3", { 280 | className: "artist" 281 | }, processArtistName(activeMusic.artist))), _react.default.createElement("div", { 282 | className: "time-and-volume" 283 | }, _react.default.createElement("div", { 284 | className: "time-remaining" 285 | }, "-", formatTime(leftTime)), _react.default.createElement("div", { 286 | className: "volume-control" 287 | }, _react.default.createElement("i", { 288 | className: "volume-icon fa fa-volume-up" 289 | }), _react.default.createElement("div", { 290 | className: "volume-bar" 291 | }, _react.default.createElement(_Progress.default, { 292 | percent: volume, 293 | onClick: this.handleAdjustVolume 294 | })))), _react.default.createElement(_Progress.default, { 295 | percent: progress, 296 | strokeColor: progressColor, 297 | onClick: this.handleAdjustProgress 298 | }), _react.default.createElement("div", { 299 | className: "controls" 300 | }, _react.default.createElement("button", { 301 | type: "button", 302 | className: "fa fa-".concat(playModeClass), 303 | style: btnStyle, 304 | onClick: this.handleChangePlayMode 305 | }), _react.default.createElement("button", { 306 | type: "button", 307 | className: "fa fa-step-backward", 308 | style: btnStyle, 309 | onClick: this.handlePrev 310 | }), _react.default.createElement("button", { 311 | type: "button", 312 | className: "fa fa-".concat(play ? 'pause' : 'play'), 313 | style: btnStyle, 314 | onClick: this.handleToggle 315 | }), _react.default.createElement("button", { 316 | type: "button", 317 | className: "fa fa-step-forward", 318 | style: btnStyle, 319 | onClick: this.handleNext 320 | }))), _react.default.createElement("div", { 321 | className: "player-cover", 322 | style: { 323 | backgroundImage: "url(".concat(activeMusic.cover, ")") 324 | } 325 | })); 326 | } 327 | }]); 328 | 329 | return MusicPlayer; 330 | }(_react.Component); 331 | 332 | exports.default = MusicPlayer; 333 | 334 | _defineProperty(MusicPlayer, "propTypes", { 335 | playlist: _propTypes.default.arrayOf(_propTypes.default.shape({ 336 | url: _propTypes.default.string, 337 | cover: _propTypes.default.string, 338 | title: _propTypes.default.string, 339 | artist: _propTypes.default.arrayOf(_propTypes.default.string) 340 | })).isRequired, 341 | mode: _propTypes.default.oneOf(['horizontal', 'vertical']), 342 | width: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), 343 | autoplay: _propTypes.default.bool, 344 | progressColor: _propTypes.default.string, 345 | btnColor: _propTypes.default.string, 346 | style: _propTypes.default.object 347 | }); 348 | 349 | _defineProperty(MusicPlayer, "defaultProps", { 350 | mode: 'horizontal', 351 | width: '100%', 352 | autoplay: false, 353 | progressColor: '#66cccc', 354 | btnColor: '#4a4a4a', 355 | style: {} 356 | }); -------------------------------------------------------------------------------- /dist/MusicPlayer.scss: -------------------------------------------------------------------------------- 1 | @import '~normalize.css/normalize.css'; 2 | @import '~font-awesome/css/font-awesome.css'; 3 | 4 | $font-weight: 400 !default; 5 | $font-size-lg: 1.5em !default; 6 | $font-size-md: 1em !default; 7 | $font-size-sm: 0.8em !default; 8 | $player-time-width: 3.2em !default; 9 | $player-title-color: #030303 !default; 10 | $player-artist-color: #4a4a4a !default; 11 | $player-time-color: #9b9b9b !default; 12 | $player-volume-width: 50px !default; 13 | $player-control-font-size: 28px !default; 14 | $player-cover-size: 240px !default; 15 | 16 | * { 17 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, PingFang SC, Source Han Sans CN, 18 | Hiragino Sans GB, Microsoft YaHei, WenQuanYi Micro Hei, sans-serif; 19 | text-rendering: optimizeLegibility; 20 | -webkit-font-smoothing: antialiased; 21 | -moz-osx-font-smoothing: grayscale; 22 | } 23 | 24 | .player { 25 | max-width: 800px; 26 | display: flex; 27 | 28 | .player-control { 29 | flex: 1; 30 | 31 | .music-info { 32 | .title, 33 | .artist { 34 | font-weight: $font-weight; 35 | text-overflow: ellipsis; 36 | overflow: hidden; 37 | white-space: nowrap; 38 | } 39 | 40 | .title { 41 | margin-bottom: 0; 42 | color: $player-title-color; 43 | font-size: $font-size-lg; 44 | } 45 | 46 | .artist { 47 | color: $player-artist-color; 48 | font-size: $font-size-md; 49 | } 50 | } 51 | 52 | .time-and-volume { 53 | display: flex; 54 | color: $player-time-color; 55 | font-size: $font-size-sm; 56 | line-height: 2; 57 | 58 | .time-remaining { 59 | width: $player-time-width; 60 | margin-right: 10px; 61 | text-align: left; 62 | font-weight: $font-weight; 63 | } 64 | 65 | .volume-control { 66 | display: flex; 67 | align-items: center; 68 | 69 | .volume-icon { 70 | margin-right: 4px; 71 | color: $player-time-color; 72 | cursor: pointer; 73 | } 74 | 75 | .volume-bar { 76 | width: $player-volume-width; 77 | opacity: 0; 78 | transition: opacity 0.5s linear; 79 | } 80 | 81 | &:hover { 82 | .volume-bar { 83 | opacity: 1; 84 | } 85 | } 86 | } 87 | } 88 | 89 | .controls { 90 | margin-top: 30px; 91 | display: flex; 92 | justify-content: space-between; 93 | 94 | button { 95 | width: 50px; 96 | margin: 0; 97 | padding: 0; 98 | font-size: $player-control-font-size; 99 | background-color: transparent; 100 | border: none; 101 | box-shadow: none; 102 | outline: none; 103 | user-select: none; 104 | touch-action: manipulation; 105 | cursor: pointer; 106 | 107 | &:first-child { 108 | margin-right: auto; 109 | } 110 | } 111 | } 112 | } 113 | 114 | .player-cover { 115 | width: $player-cover-size; 116 | height: $player-cover-size; 117 | margin-left: 20px; 118 | flex: none; 119 | border-radius: 50%; 120 | background-size: cover; 121 | background-position: center; 122 | background-repeat: no-repeat; 123 | } 124 | 125 | &.vertical { 126 | flex-direction: column-reverse; 127 | 128 | .player-control { 129 | .music-info { 130 | text-align: center; 131 | } 132 | 133 | .controls { 134 | justify-content: space-evenly; 135 | 136 | button { 137 | &:first-child { 138 | margin: 0; 139 | } 140 | } 141 | } 142 | } 143 | 144 | .player-cover { 145 | margin: 0 auto; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /dist/components/Progress/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | var _react = _interopRequireWildcard(require("react")); 9 | 10 | var _propTypes = _interopRequireDefault(require("prop-types")); 11 | 12 | require("./index.scss"); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 15 | 16 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } 17 | 18 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 19 | 20 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 21 | 22 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 23 | 24 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 25 | 26 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 27 | 28 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 29 | 30 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 31 | 32 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 33 | 34 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 35 | 36 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 37 | 38 | var Progress = 39 | /*#__PURE__*/ 40 | function (_Component) { 41 | _inherits(Progress, _Component); 42 | 43 | function Progress() { 44 | var _this; 45 | 46 | _classCallCheck(this, Progress); 47 | 48 | _this = _possibleConstructorReturn(this, _getPrototypeOf(Progress).call(this)); 49 | 50 | _defineProperty(_assertThisInitialized(_this), "onClick", function (_ref) { 51 | var clientX = _ref.clientX; 52 | var onClick = _this.props.onClick; 53 | var progressRef = _this.progressContainer.current; 54 | var progress = (clientX - progressRef.getBoundingClientRect().left) / progressRef.clientWidth; 55 | onClick(progress); 56 | }); 57 | 58 | _defineProperty(_assertThisInitialized(_this), "onKeyDown", function (_ref2) { 59 | var keyCode = _ref2.keyCode; 60 | var _this$props = _this.props, 61 | percent = _this$props.percent, 62 | onClick = _this$props.onClick; 63 | 64 | switch (keyCode) { 65 | case 37: 66 | case 40: 67 | onClick(Math.max(percent - 0.05, 0)); 68 | break; 69 | 70 | case 38: 71 | case 39: 72 | onClick(Math.min(percent + 0.05, 0.9999)); 73 | break; 74 | 75 | default: 76 | break; 77 | } 78 | }); 79 | 80 | _this.progressContainer = _react.default.createRef(); 81 | return _this; 82 | } 83 | 84 | _createClass(Progress, [{ 85 | key: "render", 86 | value: function render() { 87 | var _this$props2 = this.props, 88 | percent = _this$props2.percent, 89 | strokeColor = _this$props2.strokeColor, 90 | strokeWidth = _this$props2.strokeWidth; 91 | return _react.default.createElement("div", { 92 | ref: this.progressContainer, 93 | role: "progressbar", 94 | tabIndex: "-1", 95 | className: "progress", 96 | style: { 97 | height: "".concat(strokeWidth, "px") 98 | }, 99 | onClick: this.onClick, 100 | onKeyDown: this.onKeyDown 101 | }, _react.default.createElement("div", { 102 | className: "progress-inner", 103 | style: { 104 | width: "".concat(percent * 100, "%"), 105 | backgroundColor: strokeColor 106 | } 107 | })); 108 | } 109 | }]); 110 | 111 | return Progress; 112 | }(_react.Component); 113 | 114 | exports.default = Progress; 115 | 116 | _defineProperty(Progress, "propTypes", { 117 | percent: _propTypes.default.number, 118 | strokeColor: _propTypes.default.string, 119 | strokeWidth: _propTypes.default.number 120 | }); 121 | 122 | _defineProperty(Progress, "defaultProps", { 123 | percent: 0, 124 | strokeColor: '#9b9b9b', 125 | strokeWidth: 2 126 | }); -------------------------------------------------------------------------------- /dist/components/Progress/index.scss: -------------------------------------------------------------------------------- 1 | .progress { 2 | width: 100%; 3 | background-color: #dadada; 4 | outline: none; 5 | cursor: pointer; 6 | 7 | .progress-inner { 8 | height: 100%; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-responsive-music-player", 3 | "version": "0.4.3", 4 | "description": "A responsive music player built with React", 5 | "main": "dist/MusicPlayer.js", 6 | "module": "dist/MusicPlayer.js", 7 | "babel": { 8 | "presets": [ 9 | "@babel/preset-env", 10 | "@babel/preset-react" 11 | ], 12 | "plugins": [ 13 | "@babel/plugin-proposal-class-properties" 14 | ] 15 | }, 16 | "keywords": [ 17 | "react", 18 | "react-component", 19 | "music", 20 | "player", 21 | "audio" 22 | ], 23 | "author": "Alick Zhang", 24 | "license": "MIT", 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/alickzhang/react-responsive-music-player.git" 28 | }, 29 | "homepage": "http://alickzhang.github.io/react-responsive-music-player", 30 | "bugs": { 31 | "url": "https://github.com/alickzhang/react-responsive-music-player/issues" 32 | }, 33 | "dependencies": { 34 | "classnames": "^2.2.6", 35 | "font-awesome": "^4.7.0", 36 | "gh-pages": "^2.0.1", 37 | "node-sass": "^4.11.0", 38 | "normalize.css": "^8.0.1", 39 | "prop-types": "^15.7.2", 40 | "react": "^16.8.6", 41 | "react-dom": "^16.8.6", 42 | "react-scripts": "^2.1.8" 43 | }, 44 | "devDependencies": { 45 | "@babel/cli": "^7.4.3", 46 | "@babel/plugin-proposal-class-properties": "^7.4.0", 47 | "@babel/preset-env": "^7.4.3", 48 | "@babel/preset-react": "^7.0.0", 49 | "babel-plugin-import": "^1.11.0", 50 | "eslint-config-airbnb": "^17.1.0", 51 | "eslint-config-prettier": "^4.1.0", 52 | "eslint-plugin-import": "^2.17.2", 53 | "eslint-plugin-jsx-a11y": "^6.2.1", 54 | "eslint-plugin-react": "^7.12.4", 55 | "prettier": "^1.16.4", 56 | "stylelint": "^10.0.1", 57 | "stylelint-config-recommended-scss": "^3.2.0", 58 | "stylelint-scss": "^3.6.0" 59 | }, 60 | "scripts": { 61 | "start": "react-scripts start", 62 | "build": "react-scripts build", 63 | "test": "react-scripts test", 64 | "eslint": "./node_modules/.bin/eslint --quiet ./src/", 65 | "eject": "react-scripts eject", 66 | "publish:npm": "NODE_ENV=production && rm -rf dist && mkdir dist && npx babel src --out-dir dist --copy-files && rm -rf dist/examples && rm -rf dist/index.js", 67 | "predeploy": "npm run build", 68 | "deploy": "gh-pages -d build" 69 | }, 70 | "browserslist": [ 71 | ">0.2%", 72 | "not dead", 73 | "not ie <= 11", 74 | "not op_mini all" 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | React Responsive Music Player 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/MusicPlayer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import Progress from './components/Progress'; 5 | import './MusicPlayer.scss'; 6 | 7 | const formatTime = time => { 8 | /* eslint no-restricted-globals: off */ 9 | if (isNaN(time) || time === 0) { 10 | return ''; 11 | } 12 | const mins = Math.floor(time / 60); 13 | const secs = (time % 60).toFixed(); 14 | return `${mins < 10 ? '0' : ''}${mins}:${secs < 10 ? '0' : ''}${secs}`; 15 | }; 16 | 17 | const processArtistName = artistList => artistList.join(' / '); 18 | 19 | const getPlayModeClass = playMode => { 20 | if (playMode === 'loop') return 'refresh'; 21 | if (playMode === 'random') return 'random'; 22 | return 'repeat'; 23 | }; 24 | 25 | export default class MusicPlayer extends Component { 26 | static propTypes = { 27 | playlist: PropTypes.arrayOf( 28 | PropTypes.shape({ 29 | url: PropTypes.string, 30 | cover: PropTypes.string, 31 | title: PropTypes.string, 32 | artist: PropTypes.arrayOf(PropTypes.string) 33 | }) 34 | ).isRequired, 35 | mode: PropTypes.oneOf(['horizontal', 'vertical']), 36 | width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 37 | autoplay: PropTypes.bool, 38 | progressColor: PropTypes.string, 39 | btnColor: PropTypes.string, 40 | style: PropTypes.object 41 | }; 42 | 43 | static defaultProps = { 44 | mode: 'horizontal', 45 | width: '100%', 46 | autoplay: false, 47 | progressColor: '#66cccc', 48 | btnColor: '#4a4a4a', 49 | style: {} 50 | }; 51 | 52 | constructor(props) { 53 | super(props); 54 | this.state = { 55 | activeMusicIndex: 0, 56 | leftTime: 0, 57 | play: props.autoplay || false, 58 | playMode: 'loop', 59 | progress: 0, 60 | volume: 1 61 | }; 62 | this.modeList = ['loop', 'random', 'repeat']; 63 | this.audioContainer = React.createRef(); 64 | } 65 | 66 | componentDidMount() { 67 | this.audioContainer.current.addEventListener('timeupdate', this.updateProgress); 68 | this.audioContainer.current.addEventListener('ended', this.end); 69 | } 70 | 71 | componentWillUnmount() { 72 | this.audioContainer.current.removeEventListener('timeupdate', this.updateProgress); 73 | this.audioContainer.current.removeEventListener('ended', this.end); 74 | } 75 | 76 | updateProgress = () => { 77 | const { duration, currentTime } = this.audioContainer.current; 78 | const progress = currentTime / duration || 0; 79 | this.setState({ progress, leftTime: duration - currentTime }); 80 | }; 81 | 82 | end = () => { 83 | this.handleNext(); 84 | }; 85 | 86 | handleAdjustProgress = value => { 87 | const currentTime = this.audioContainer.current.duration * value; 88 | this.audioContainer.current.currentTime = currentTime; 89 | this.setState({ play: true, progress: value }, () => this.audioContainer.current.play()); 90 | }; 91 | 92 | handleAdjustVolume = value => { 93 | const volume = value < 0 ? 0 : value; 94 | this.audioContainer.current.volume = volume; 95 | this.setState({ volume }); 96 | }; 97 | 98 | handleToggle = () => { 99 | const { play } = this.state; 100 | if (play) { 101 | this.audioContainer.current.pause(); 102 | } else { 103 | this.audioContainer.current.play(); 104 | } 105 | this.setState(({ play }) => ({ play: !play })); 106 | }; 107 | 108 | handlePrev = () => { 109 | const { playlist } = this.props; 110 | const { playMode, activeMusicIndex } = this.state; 111 | if (playMode === 'repeat') { 112 | this.playMusic(activeMusicIndex); 113 | } else if (playMode === 'loop') { 114 | const total = playlist.length; 115 | const index = activeMusicIndex > 0 ? activeMusicIndex - 1 : total - 1; 116 | this.playMusic(index); 117 | } else if (playMode === 'random') { 118 | let randomIndex = Math.floor(Math.random() * playlist.length); 119 | while (randomIndex === activeMusicIndex) { 120 | randomIndex = Math.floor(Math.random() * playlist.length); 121 | } 122 | this.playMusic(randomIndex); 123 | } else { 124 | this.setState({ play: false }); 125 | } 126 | }; 127 | 128 | handleNext = () => { 129 | const { playlist } = this.props; 130 | const { playMode, activeMusicIndex } = this.state; 131 | if (playMode === 'repeat') { 132 | this.playMusic(activeMusicIndex); 133 | } else if (playMode === 'loop') { 134 | const total = playlist.length; 135 | const index = activeMusicIndex < total - 1 ? activeMusicIndex + 1 : 0; 136 | this.playMusic(index); 137 | } else if (playMode === 'random') { 138 | let randomIndex = Math.floor(Math.random() * playlist.length); 139 | while (randomIndex === activeMusicIndex) { 140 | randomIndex = Math.floor(Math.random() * playlist.length); 141 | } 142 | this.playMusic(randomIndex); 143 | } else { 144 | this.setState({ play: false }); 145 | } 146 | }; 147 | 148 | handleChangePlayMode = () => { 149 | const { playMode } = this.state; 150 | let index = this.modeList.indexOf(playMode); 151 | index = (index + 1) % this.modeList.length; 152 | this.setState({ playMode: this.modeList[index] }); 153 | }; 154 | 155 | playMusic = index => { 156 | this.setState({ activeMusicIndex: index, leftTime: 0, play: true, progress: 0 }, () => { 157 | this.audioContainer.current.currentTime = 0; 158 | this.audioContainer.current.play(); 159 | }); 160 | }; 161 | 162 | render() { 163 | const { playlist, mode, width, progressColor, btnColor, style } = this.props; 164 | const { play, progress, leftTime, volume, activeMusicIndex, playMode } = this.state; 165 | const activeMusic = playlist[activeMusicIndex]; 166 | const playModeClass = getPlayModeClass(playMode); 167 | const btnStyle = { color: btnColor }; 168 | 169 | return ( 170 |
174 | 177 |
178 |
179 |

{activeMusic.title}

180 |

{processArtistName(activeMusic.artist)}

181 |
182 |
183 |
-{formatTime(leftTime)}
184 |
185 | 186 |
187 | 188 |
189 |
190 |
191 | 192 |
193 |
208 |
209 |
210 |
211 | ); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/MusicPlayer.scss: -------------------------------------------------------------------------------- 1 | @import '~normalize.css/normalize.css'; 2 | @import '~font-awesome/css/font-awesome.css'; 3 | 4 | $font-weight: 400 !default; 5 | $font-size-lg: 1.5em !default; 6 | $font-size-md: 1em !default; 7 | $font-size-sm: 0.8em !default; 8 | $player-time-width: 3.2em !default; 9 | $player-title-color: #030303 !default; 10 | $player-artist-color: #4a4a4a !default; 11 | $player-time-color: #9b9b9b !default; 12 | $player-volume-width: 50px !default; 13 | $player-control-font-size: 28px !default; 14 | $player-cover-size: 240px !default; 15 | 16 | * { 17 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, PingFang SC, Source Han Sans CN, 18 | Hiragino Sans GB, Microsoft YaHei, WenQuanYi Micro Hei, sans-serif; 19 | text-rendering: optimizeLegibility; 20 | -webkit-font-smoothing: antialiased; 21 | -moz-osx-font-smoothing: grayscale; 22 | } 23 | 24 | .player { 25 | max-width: 800px; 26 | display: flex; 27 | 28 | .player-control { 29 | flex: 1; 30 | 31 | .music-info { 32 | .title, 33 | .artist { 34 | font-weight: $font-weight; 35 | text-overflow: ellipsis; 36 | overflow: hidden; 37 | white-space: nowrap; 38 | } 39 | 40 | .title { 41 | margin-bottom: 0; 42 | color: $player-title-color; 43 | font-size: $font-size-lg; 44 | } 45 | 46 | .artist { 47 | color: $player-artist-color; 48 | font-size: $font-size-md; 49 | } 50 | } 51 | 52 | .time-and-volume { 53 | display: flex; 54 | color: $player-time-color; 55 | font-size: $font-size-sm; 56 | line-height: 2; 57 | 58 | .time-remaining { 59 | width: $player-time-width; 60 | margin-right: 10px; 61 | text-align: left; 62 | font-weight: $font-weight; 63 | } 64 | 65 | .volume-control { 66 | display: flex; 67 | align-items: center; 68 | 69 | .volume-icon { 70 | margin-right: 4px; 71 | color: $player-time-color; 72 | cursor: pointer; 73 | } 74 | 75 | .volume-bar { 76 | width: $player-volume-width; 77 | opacity: 0; 78 | transition: opacity 0.5s linear; 79 | } 80 | 81 | &:hover { 82 | .volume-bar { 83 | opacity: 1; 84 | } 85 | } 86 | } 87 | } 88 | 89 | .controls { 90 | margin-top: 30px; 91 | display: flex; 92 | justify-content: space-between; 93 | 94 | button { 95 | width: 50px; 96 | margin: 0; 97 | padding: 0; 98 | font-size: $player-control-font-size; 99 | background-color: transparent; 100 | border: none; 101 | box-shadow: none; 102 | outline: none; 103 | user-select: none; 104 | touch-action: manipulation; 105 | cursor: pointer; 106 | 107 | &:first-child { 108 | margin-right: auto; 109 | } 110 | } 111 | } 112 | } 113 | 114 | .player-cover { 115 | width: $player-cover-size; 116 | height: $player-cover-size; 117 | margin-left: 20px; 118 | flex: none; 119 | border-radius: 50%; 120 | background-size: cover; 121 | background-position: center; 122 | background-repeat: no-repeat; 123 | } 124 | 125 | &.vertical { 126 | flex-direction: column-reverse; 127 | 128 | .player-control { 129 | .music-info { 130 | text-align: center; 131 | } 132 | 133 | .controls { 134 | justify-content: space-evenly; 135 | 136 | button { 137 | &:first-child { 138 | margin: 0; 139 | } 140 | } 141 | } 142 | } 143 | 144 | .player-cover { 145 | margin: 0 auto; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/components/Progress/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import './index.scss'; 4 | 5 | export default class Progress extends Component { 6 | static propTypes = { 7 | percent: PropTypes.number, 8 | strokeColor: PropTypes.string, 9 | strokeWidth: PropTypes.number 10 | }; 11 | 12 | static defaultProps = { 13 | percent: 0, 14 | strokeColor: '#9b9b9b', 15 | strokeWidth: 2 16 | }; 17 | 18 | constructor() { 19 | super(); 20 | this.progressContainer = React.createRef(); 21 | } 22 | 23 | onClick = ({ clientX }) => { 24 | const { onClick } = this.props; 25 | const progressRef = this.progressContainer.current; 26 | const progress = (clientX - progressRef.getBoundingClientRect().left) / progressRef.clientWidth; 27 | onClick(progress); 28 | }; 29 | 30 | onKeyDown = ({ keyCode }) => { 31 | const { percent, onClick } = this.props; 32 | switch (keyCode) { 33 | case 37: 34 | case 40: 35 | onClick(Math.max(percent - 0.05, 0)); 36 | break; 37 | case 38: 38 | case 39: 39 | onClick(Math.min(percent + 0.05, 0.9999)); 40 | break; 41 | default: 42 | break; 43 | } 44 | }; 45 | 46 | render() { 47 | const { percent, strokeColor, strokeWidth } = this.props; 48 | return ( 49 |
58 |
59 |
60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/components/Progress/index.scss: -------------------------------------------------------------------------------- 1 | .progress { 2 | width: 100%; 3 | background-color: #dadada; 4 | outline: none; 5 | cursor: pointer; 6 | 7 | .progress-inner { 8 | height: 100%; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/examples/BasicPlayer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Player from '../../MusicPlayer'; 3 | import playlist from '../playlist'; 4 | 5 | export default () => ( 6 |
7 | 8 |
9 | ); 10 | -------------------------------------------------------------------------------- /src/examples/ExampleComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import BasicPlayer from './BasicPlayer'; 3 | import VerticalPlayer from './VerticalPlayer'; 4 | 5 | export default () => ( 6 |
7 | 8 | 9 |
10 | ); 11 | -------------------------------------------------------------------------------- /src/examples/VerticalPlayer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Player from '../../MusicPlayer'; 3 | import playlist from '../playlist'; 4 | 5 | export default () => ; 6 | -------------------------------------------------------------------------------- /src/examples/playlist.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | url: 'http://res.cloudinary.com/alick/video/upload/v1502689683/Luis_Fonsi_-_Despacito_ft._Daddy_Yankee_uyvqw9.mp3', 4 | cover: 'http://res.cloudinary.com/alick/image/upload/v1502689731/Despacito_uvolhp.jpg', 5 | title: 'Despacito', 6 | artist: ['Luis Fonsi', 'Daddy Yankee'] 7 | }, 8 | { 9 | url: 'http://res.cloudinary.com/alick/video/upload/v1502375674/Bedtime_Stories.mp3', 10 | cover: 'http://res.cloudinary.com/alick/image/upload/v1502375978/bedtime_stories_bywggz.jpg', 11 | title: 'Bedtime Stories', 12 | artist: ['Jay Chou'] 13 | }, 14 | { 15 | url: 'http://res.cloudinary.com/alick/video/upload/v1502444212/Actor_ud8ccw.mp3', 16 | cover: 'http://res.cloudinary.com/alick/image/upload/v1502444304/actor_umzdur.jpg', 17 | title: '演员', 18 | artist: ['薛之谦'] 19 | }, 20 | { 21 | url: 'http://res.cloudinary.com/alick/video/upload/v1502444215/Bridge_of_Fate_aaksg1.mp3', 22 | cover: 'http://res.cloudinary.com/alick/image/upload/v1502444306/Bridge_of_Fate_o36rem.jpg', 23 | title: 'Bridge of Fate', 24 | artist: ['王力宏', '谭维维'] 25 | }, 26 | { 27 | url: 'http://res.cloudinary.com/alick/video/upload/v1502444222/Goodbye_byaom5.mp3', 28 | cover: 'http://res.cloudinary.com/alick/image/upload/v1502444310/Goodbye_hpubmk.jpg', 29 | title: 'Goodbye', 30 | artist: ['G.E.M.'] 31 | } 32 | ]; 33 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import ExampleComponent from './examples/ExampleComponent'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | --------------------------------------------------------------------------------