├── .babelrc ├── .gitignore..git ├── LICENSE ├── README.md ├── index.html ├── package.json ├── public ├── Consolation.mp3 ├── illenium_cover.jpg ├── miro_cover.jpg ├── with_u_newVersion_4_FINAL.mp3 ├── without_you.jpg └── without_you.mp3 ├── react-audio-component.sketch ├── server.js ├── src ├── actions │ └── index.js ├── assets │ └── default_cover.txt ├── audio │ └── ReactAudio.js ├── components │ ├── ArtistInfo.css │ ├── ArtistInfo.js │ ├── AudioControlButton.css │ ├── AudioControlButton.js │ ├── Utilities.css │ ├── Utilities.js │ └── svg │ │ ├── HeartIcon.js │ │ ├── NextIcon.js │ │ ├── PauseIcon.js │ │ ├── PlayIcon.js │ │ ├── PreviousIcon.js │ │ ├── RepeatIcon.js │ │ ├── ShareIcon.js │ │ └── SpeakerIcon.js ├── constants │ └── ActionTypes.js ├── containers │ ├── App.css │ └── App.js ├── css │ ├── ReactSlider.css │ ├── index.css │ └── variables.css ├── index.js ├── reducers │ ├── audio.js │ └── index.js ├── store │ ├── configureStore.js │ └── songs.json └── utils │ └── detection.js ├── webpack.hot.js └── webpack.prod.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets" : ["es2015", "react", "stage-0"], 3 | "plugins": ["transform-decorators-legacy"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore..git: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 J.S. Leonard 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 Audio Component Example Project 2 | 3 | A beautiful example project demonstrating how to build an audio player in React. Desktop optimized though it works on mobile down to iPhone 5 resolutions. 4 | 5 | ## The Player 6 | 7 | ![Audio Player](http://leonardsouza.com/public/images/portfolio/audio_player.jpg) 8 | 9 | Demo [here](http://leonardsouza.com/react-audio-example.html). Check it out! 10 | 11 | ### Quick Start 12 | 13 | `npm install` 14 | 15 | `npm start` 16 | 17 | `open localhost:3000` 18 | 19 | 20 | _Rest of README forthcoming_ 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Audio Player 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-audio-component", 3 | "version": "1.0.0", 4 | "description": "A beautiful audio player built with React", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "deploy": "webpack -p --config webpack.prod.js --output-file audio_player.js && mv ./public/audio_player.js ../portfolio/jslauthor.github.io/public", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/jslauthor/react-audio-component.git" 14 | }, 15 | "keywords": [ 16 | "audio", 17 | "html5", 18 | "react" 19 | ], 20 | "author": "J.S. Leonard", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/jslauthor/react-audio-component/issues" 24 | }, 25 | "homepage": "https://github.com/jslauthor/react-audio-component#readme", 26 | "devDependencies": { 27 | "autoprefixer": "^6.3.6", 28 | "css-loader": "^0.23.1", 29 | "postcss-bem": "^0.4.1", 30 | "postcss-color-function": "^2.0.1", 31 | "postcss-custom-media": "^5.0.1", 32 | "postcss-import": "^8.1.2", 33 | "postcss-loader": "^0.9.1", 34 | "postcss-mixins": "^4.0.2", 35 | "postcss-nested": "^1.0.0", 36 | "postcss-sass-colors": "0.0.2", 37 | "postcss-simple-vars": "^2.0.0", 38 | "precss": "^1.4.0", 39 | "react-hot-loader": "^1.3.0", 40 | "style-loader": "^0.13.1", 41 | "webpack": "^1.13.1", 42 | "webpack-dev-server": "^1.14.1" 43 | }, 44 | "dependencies": { 45 | "babel-loader": "^6.2.4", 46 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 47 | "babel-preset-es2015": "^6.9.0", 48 | "babel-preset-react": "^6.5.0", 49 | "babel-preset-stage-0": "^6.5.0", 50 | "imports-loader": "^0.6.5", 51 | "json-loader": "^0.5.4", 52 | "lodash": "^4.13.1", 53 | "marked": "^0.3.5", 54 | "raf": "^3.2.0", 55 | "raw-loader": "^0.5.1", 56 | "rc-progress": "^1.0.4", 57 | "rc-slider": "^3.7.1", 58 | "rc-tooltip": "^3.3.2", 59 | "react": "^15.1.0", 60 | "react-addons-css-transition-group": "^15.1.0", 61 | "react-dom": "^15.1.0", 62 | "react-marquee": "^0.1.0", 63 | "react-redux": "^4.4.5", 64 | "react-slider": "^0.6.1", 65 | "react-swipeable": "^3.5.0", 66 | "redux": "^3.5.2" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /public/Consolation.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jslauthor/react-audio-component/6d8589f573c9ccabfc91f1fcf100c758fedf8e9c/public/Consolation.mp3 -------------------------------------------------------------------------------- /public/illenium_cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jslauthor/react-audio-component/6d8589f573c9ccabfc91f1fcf100c758fedf8e9c/public/illenium_cover.jpg -------------------------------------------------------------------------------- /public/miro_cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jslauthor/react-audio-component/6d8589f573c9ccabfc91f1fcf100c758fedf8e9c/public/miro_cover.jpg -------------------------------------------------------------------------------- /public/with_u_newVersion_4_FINAL.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jslauthor/react-audio-component/6d8589f573c9ccabfc91f1fcf100c758fedf8e9c/public/with_u_newVersion_4_FINAL.mp3 -------------------------------------------------------------------------------- /public/without_you.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jslauthor/react-audio-component/6d8589f573c9ccabfc91f1fcf100c758fedf8e9c/public/without_you.jpg -------------------------------------------------------------------------------- /public/without_you.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jslauthor/react-audio-component/6d8589f573c9ccabfc91f1fcf100c758fedf8e9c/public/without_you.mp3 -------------------------------------------------------------------------------- /react-audio-component.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jslauthor/react-audio-component/6d8589f573c9ccabfc91f1fcf100c758fedf8e9c/react-audio-component.sketch -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 2 | var webpack = require('webpack'); 3 | var WebpackDevServer = require('webpack-dev-server'); 4 | var config = require('./webpack.hot'); 5 | 6 | new WebpackDevServer(webpack(config), { 7 | publicPath: config.output.publicPath, 8 | hot: true, 9 | historyApiFallback: true 10 | }).listen(3000, '0.0.0.0', function (err, result) { 11 | if (err) { 12 | return console.log(err); 13 | } 14 | 15 | console.log('Listening at http://0.0.0.0:3000/'); 16 | }); 17 | -------------------------------------------------------------------------------- /src/actions/index.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/ActionTypes' 2 | import data from '../store/songs.json'; 3 | 4 | export function retrieveSongs() { 5 | // This could be written as a thunk or saga 6 | return { type: types.INITIALIZE, songs: data.songs } 7 | } 8 | 9 | export function play(audio) { 10 | if (audio.paused) 11 | audio.play(); 12 | else 13 | audio.pause(); 14 | 15 | return { type: types.PLAY, audio } 16 | } 17 | 18 | export function pause(audio) { 19 | audio.pause(); 20 | return { type: types.PAUSE, audio } 21 | } 22 | 23 | function resetAudio(audio) { 24 | // need to reset the song if it's the same file 25 | audio.currentTime = 0; 26 | const src = audio.src; 27 | audio.src = null; 28 | audio.src = src; 29 | } 30 | 31 | export function next(audio) { 32 | resetAudio(audio) 33 | return { type: types.NEXT, audio } 34 | } 35 | 36 | export function previous(audio) { 37 | resetAudio(audio) 38 | return { type: types.PREVIOUS, audio } 39 | } 40 | 41 | export function updateVolume(audio, volume) { 42 | audio.volume = volume/100; 43 | return { type: types.UPDATE_VOLUME, volume } 44 | } 45 | 46 | export function setTime(audio) { 47 | const percent = audio.currentTime / audio.duration; 48 | return { type: types.SET_TIME, audio } 49 | } 50 | 51 | export function setProgress(audio) { 52 | return { type: types.SET_PROGRESS, audio } 53 | } 54 | 55 | export function setError(audio) { 56 | return { type: types.ERROR, audio } 57 | } 58 | 59 | export function updatePosition(audio, percent) { 60 | audio.currentTime = percent * audio.duration; 61 | return { type: types.UPDATE_POSITION, audio } 62 | } 63 | 64 | export function toggleFavorite(id) { 65 | return { type: types.TOGGLE_FAVORITE, id } 66 | } 67 | 68 | export function toggleRepeat() { 69 | return { type: types.TOGGLE_REPEAT } 70 | } 71 | 72 | export function toggleLoop(audio) { 73 | audio.loop = !audio.loop; 74 | return { type: types.TOGGLE_LOOP, audio } 75 | } 76 | -------------------------------------------------------------------------------- /src/assets/default_cover.txt: -------------------------------------------------------------------------------- 1 | data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBARXhpZgAATU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAB9KADAAQAAAABAAAB9AAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgB9AH0AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBQQEBAQEBQYFBQUFBQUGBgYGBgYGBgcHBwcHBwgICAgICQkJCQkJCQkJCf/bAEMBAQEBAgICBAICBAkGBQYJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCf/dAAQAIP/aAAwDAQACEQMRAD8A/l/ooorxznCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9D+X+iiivHOcKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0f5f6KKK8c5wooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//S/l/ooorxznCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9P+X+iiivHOcKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/1P5f6KKK8c5wooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//V/l/ooorxznCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9b+X+iiivHOcKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/1/5f6KKK8c5wooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//Q/l/ooorxznCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9H+X+iiivHOcKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0v5f6KKK8c5wooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAor720vwL/wAE9ZdNt5NU8aeIkuWiQyqIcAOQNwAFk4GD6M31PWuL+KXhL9izS/BF5e/CjxTreo68uz7Nb3UWIXy4D7ybWHAC5Od+c44PSuGOPTly8svuZ8Zh+NadSsqKw9ZXdrulJL1btovM+PaK2vDkOhXHiGwg8UTS2+mPcRLdywqGkSAsBIyKeCwXJA9a+6v+EC/4Jz/9Dt4j/wC/R/8AkCta+JVO1036K56WdcRQwMoxlSqTv/JCUrettj8+6K+m/jd4b/ZW0Xw9az/AnxDq2r6k9xtnivo9sSwbWywY28B3btoAG7IJ6Y58j+F9j8OdS8cWVl8WL2607QXL/abizUPMmEJTAKvwXwCQjHHaqp11KHPZ/dr9x0YPOo1sI8Wqc0lf3XFqWnaL1d+nc4Civ0E/4QL/AIJz/wDQ7eI/+/R/+QKofHn9lb4deF/g5Y/Hf4K65c6hocxVZE1LasziSTyleLEUROG4ZCmcfMDgGuaOZQ5lGSav3TR89R8QMG69OhWp1Kbm+WLnTlFOT2V2t30PgqiiivQPugooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP//T/l/ooorxznCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK+5/wBjjwz4d1/wr8T7jXLGC8e18OymFpo1cxkpKdybgdpyqnI5GBXwxX2j+yX8QvBngjwz8R7LxXqEVjLqmgSw2iyEjzpdki7E45YlxgdT+BrhzJSdF8u+n5o+M8QKdaWU1I4dNyvDa9/jjfbyPi6iiiu4+zCiiigAooooAK9J8Q6z8VbvwFo+l+Jjfnw5bsx00TxuttuOd3ksVCseucE9682r6f8AiR8bfDPjL9nvwZ8JNOtrqPUPDckr3EsioIXEhbHlkOWP3hnKisKzfNGyvr92h4uazqKtQVOkppy1f8i5Ze8vnZfM898D/A34jfEfwXrPjrwbZi+tNCKi5jRwZ8MCxKRfeYKBk459AcHHkVet/Bj4z+Mvgb4zh8YeEJfRLm2cnyrmLPMbgf8AjrdVPI9/qL9oX4cfDH4n/D2X9qT4JSxWcHmRrrmksQr29xMwTcijgFnYZA4YHev8QrCWJlCry1F7r2fn2f6Hj4nP8Rg8xVDGxXsajShNX0k9OSfq/hls9j4CoooruPsgooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//U/l/ooorxznCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKtW1je3qytZwvKIUMkhRS2xBwWbA4UZHJ4qrX6AfsRxo3hD4rMygn/hG5RyOxjmyPxrmxeI9lTc7f1c8DifO3l2CnjFHm5eXS9t5Jfqfn/RRRXSe+FFFFABRRRQAV96z/sgap4w/Z98H/ED4SaZc6nreqmVtQXz41jVFZ1UqshTHIHQmvgqvR/D/wAYfi14T0uPQ/C/ifVdOsoiSlvbXk0USljk7UVgoyTk4HWuXFU6srOk7NM+b4kwOYVo0pZdUUJRld813GSs1Z2ae7T+RR8BfDXxv8TvELeFPAlg2oagsbymFXRDsQgMcuyrwSO9L43+G/jj4b+JB4M8a2LWGosqP5DOjEiT7hyjMvP1rH8N+LvFXg7UzrfhLUrrS7wqUM9rM8Mm1sErvQg4OBkd6PEPi7xV4t1Ya/4p1K61K+AVRcXMzyy4T7o3uS2B254rT95z9OX8Tv5cb9avePsrdnzc3re1vlc961b9jn9ovQfDWoeK9Y8OvBa6bH5so86F5DGMlmVI3YsEAy2OcevOPmOv09/Yi+J3xI8c+IPF+neNNf1HV7eLw/cSJHeXMs6K+9BuCyMwBwSMjsa/MKubCV6jnOnVtdW28z57hfOMwq4vE4LMORyp8jTgmlaSb6t7W/EKKKK7z7YKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//9X+X+iiivHOcKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr6t/Zp+MfhD4V6B480zxSZhJ4g0WSztPKTeDMVdVVuflB39TwMH8flKur8M+BvF3jKDULnwtp819HpVs13dtEuRFCn3nb2H54B44NYYmlGcHGex43EGX4bFYSVDFu0Ha7vbZprX1SOUooorc9kKKKKACiiigAooooAKKKKAO58C/Enxv8NLq9vfA9++ny6hayWVwyqjb4JMbl+dWweAQwww7EVw1fUP7LfwU8M/GzXfEOl+J57iBNL0ae9gNuVU+crKqFtytlRuJIGM8c+vy9WEKkHOUVurXPGweNws8ZXo0l+8jy8ztvdPl162V/QKKKK3PZCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/W/l/ooorxznCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK/QP9iH/kT/AIrf9i5L/wCi5q/Pyvor4EfHK0+Duj+MNKu9Pe9/4SbSpLCNkcJ5UjKyqzZByvzknHPArizClKdFxjvp+aPkeOstrYvK6mHw8bybjZek4t7+SZ860UUV2n1wUUUUAFFFFABRRRQAUUUUAfoF/wAE+CB4w8ZE/wDQuXH/AKMjr8/a1dJ17XNBkml0K8nsmuImt5TBI0Zkif78bFSNyN3U8HuKyq5qdDlqSqX3t+B8/l+SSoZhiMa5XVXk07cqa/G4UUUV0n0AUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB/9f+X+iiivHOcKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr074d/CLxn8ULHXNR8KRRvF4esnv7syOE/dICSE9WIBIHHTr0z5jX6B/sQ/8if8AFb/sXJf/AEXNXJjq7p0nOPl+Z8xxjnFXAZdPFULcycd9tZJP8Gfn5RRRXWfThRRRQAUUUUAFFFFABRRRQB9ifsb/AAw8E/FDxJ4osPG9mL2Ky0K4uIAXZPLmDIokG0j5lBOM5HtXx3X3p+wPqmmaX4t8Yy6lcR26nw3dEGRgoIV4ycZPYcmvguuGhKTr1E9tP1PjMmr1pZzjac2+VKnZdFdSvb9QoooruPswooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9D+X+iiivHOcKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr2f4S/G3xB8IdM8S6XolrBcp4l059PmM27MYcMN6bSMkBm4PGceleMV7l8Hfgfqnxh0vxRqmn30VmvhnTX1BxIpYzbAzCMYI25Cn5ucccHPGGJ9nyP2mx43EDwSwknmFvZ6Xvfurbedjw2iiitz2QooooAKKKKACiiigAooooAKK+5/2FPDPh3xN4r8X2/iKxgvkj8O3LIs8ayBSXQFl3A4OCRkc4718MVz066lUlTttb8TxMFnUa+Nr4JRs6XLr35k3+FgoooroPbCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0f5f6KKK8c5wooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACv0D/Yh/5E/wCK3/YuS/8Aouavz8r0TwJ8VPG3w2s9ZsPCN0LeLXrNrG8BRX3wvkHG4Ha2CQGHIya5MbQdSk4R8vzPmeMMmq5hl88JRaUm477aST/JHndFFFdZ9MFFFFABRRRQAUUUUAFFFFAH2j+xX8QfBvw98TeK73xnqEWnxXWgXMMLSnAkk3owRfViAcDqe1fF1WrWyvb4utlC8xiQyOEUttRerHHQDuegqrWFOgo1JTW7t+B42DyenRxlfGRfvVOW67cqaVvW4UUUVueyFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf//S/l/ooorxznCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK+pv2Z/2erT4+xeKrdrmeK90fTjcWMUOwedctu8tHL8bSVwcFeudwxz8s10nhnxl4v8FXj6h4N1W80m4lTy3lsp5IHZM52lo2UkZGcE4rDEQnKDVN2Z5GfYXFVsJOngqnJUdrNq6Wqe3mtPmejfEX9nX4z/AAn0WPxF8QNCl0+yllECzGSGVfMYEhT5TuRkA4zgV554O8G+JviD4ltfCHg60a+1K9LCGBCoLbVLtyxCgBVJJJAAFaHib4m/EjxrZpp3jLxDqWrW8b+YkV7dzTor4I3BZHYA4JGRzzXN6PrWseHtTh1rw/dzWN5bndFPbyNFKhxjKuhDA4OODRTVXk963N+BOCjmCwrWIlD2utmlLl8rptv119D6W/4Yl/ah/wChUk/8CbX/AOPV5N8S/gv8T/g9LaQ/EjSZNLN+Ha3LPHIr+Xjfho2cZXcMgnPI4q3/AML++O//AEO2vf8Agyuv/jlcd4o8deN/HEsM/jTWb7WHtwVia9uJbgoGwSFMjNtBwM461lRWI5v3jVvJP/M8zK6eequnjZ0nDryxmn5Wbk1v5Gt8OfhZ4/8Ai1rMugfDzTX1O7giM8iIyIFjBC7i0jKo5IHXJ7V7T/wxL+1D/wBCpJ/4E2v/AMer538NeLfFXgy/Oq+D9Tu9JumQxmazmkgkKEglS0bKcEgZGccV3X/C/vjv/wBDtr3/AIMrr/45RWWI5v3bVvNP/MvNqedutfAzpKH96M27/KSX4HN/ED4b+N/hZr//AAi/j/T302/8tZhE5VsxvkKwZGZSCQRweoI6iuj+GnwL+K/xggurn4caNJqcdkyrM6yRRqrOCVGZXQE4HQZx361wPiHxP4l8XakdZ8V6jc6peMoUz3czzyFV6AvIWbA7DPFaXhf4gePPA4mHgrW7/RxcY837Fcy2/mbc7d3lsu7GTjPTNayVXk0tzfgehXjmH1NKlKHttNWpcl+ul7+mp73/AMMS/tQ/9CpJ/wCBNr/8er508T+GNe8GeILrwt4otms9QsnMc8L43Iw5xkEg8cggkEciu9/4X98d/wDodte/8GV1/wDHK8y1PVNT1vUJtW1m4lu7u4YvLNM7SSOx6szsSWJ7kmooKtf9618r/wCZy5LDN1N/2jKm4205FJO/nzSeh+kH7AfgHxTbw+K/iJe2LLol3ol3ZRXTFdryqyMyBc7iMA5ONvGM5r80a7HSfiJ8QNA0ObwzoWu6hZabc7vNtILqWOCTeNrb41YI24cHI5HBrjqVDDyjUnUk97fgY5Nklehj8Vja0k1V5bJJ6KKaV7t669AooorrPqQooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/0/5f6KKK8c5wooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//U/l/ooorxznCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9X+X+iiivHOcKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/1v5f6KKK8c5wooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//X/l/ooorxznCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9D+X+iiivHOcKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0f5f6KKK8c5wooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//S/l/ooorxznCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9P+X+iiivHOcKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/1P5f6KKK8c5wooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//V/l/ooorxznCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9b+X+iiivHOcKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/2Q== 2 | -------------------------------------------------------------------------------- /src/audio/ReactAudio.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import isFunction from 'lodash/isFunction'; 4 | import partialRight from 'lodash/partialRight'; 5 | import forEach from 'lodash/forEach'; 6 | 7 | class ReactAudio extends React.Component { 8 | 9 | static propTypes = { 10 | autoplay: React.PropTypes.bool, 11 | preload: React.PropTypes.bool, 12 | source: React.PropTypes.string, 13 | loop: React.PropTypes.bool, 14 | volume: React.PropTypes.number, 15 | onTimeupdate: React.PropTypes.func, 16 | onError: React.PropTypes.func, 17 | onProgress: React.PropTypes.func, 18 | onEnded: React.PropTypes.func 19 | }; 20 | 21 | static defaultProps = { 22 | autoplay: false, 23 | preload: true, 24 | source: "", 25 | loop: false, 26 | volume: .8, 27 | onTimeupdate: null, 28 | onError: null, 29 | onProgress: null, 30 | onEnded: null 31 | }; 32 | 33 | constructor(props) { 34 | super(props) 35 | 36 | this.state = { 37 | listeners: [] 38 | }; 39 | } 40 | 41 | get audio() { 42 | if (!this.refs) 43 | return {}; 44 | 45 | return ReactDOM.findDOMNode(this.refs.audio); 46 | } 47 | 48 | set audio(a) {} 49 | 50 | handler(e, func) { 51 | if (isFunction(func)) { 52 | func(e); 53 | } 54 | } 55 | 56 | addListener = (event, func) => { 57 | var audio = ReactDOM.findDOMNode(this.refs.audio); 58 | audio.addEventListener(event, partialRight(this.handler, func)); 59 | this.state.listeners.push({event: event, func: func}); 60 | } 61 | 62 | removeAllListeners = () => { 63 | var audio = ReactDOM.findDOMNode(this.refs.audio); 64 | forEach(this.state.listeners, (obj) => { 65 | audio.removeEventListener(obj.event, obj.func); 66 | }) 67 | this.state.listeners = []; 68 | } 69 | 70 | componentDidMount() { 71 | this.addListener('timeupdate', this.props.onTimeupdate); 72 | this.addListener('progress', this.props.onProgress); 73 | this.addListener('error', this.props.onError); 74 | this.addListener('ended', this.props.onEnded); 75 | this.addListener('loadeddata', this.props.onLoadedData); 76 | } 77 | 78 | componentWillUnmount() { 79 | this.removeAllListeners(); 80 | } 81 | 82 | componentWillReceiveProps(nextProps) { 83 | if (nextProps.autoplay === true && this.props.autoplay === false) { 84 | this.audio.play(); 85 | } 86 | } 87 | 88 | togglePlay = () => { 89 | if (this.audio.paused) 90 | this.audio.play(); 91 | else 92 | this.audio.pause(); 93 | } 94 | 95 | setPlaybackPercent(percent) { 96 | this.audio.currentTime = percent * this.audio.duration; 97 | } 98 | 99 | changeCurrentTimeBy = (amount) => { 100 | this.audio.currentTime += amount; 101 | } 102 | 103 | setVolume = (percent) => { 104 | this.audio.volume = percent; 105 | } 106 | 107 | render() { 108 | return( 109 |