Last active
August 16, 2018 07:41
-
-
Save bartimaeus/8dae7765eb3489e5591da4ebb7fbedce to your computer and use it in GitHub Desktop.
Lesson 03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
- Make the Play button work | |
- Make the Pause button work | |
- Disable the play button if it's playing | |
- Disable the pause button if it's not playing | |
- Make the PlayPause button work | |
- Make the JumpForward button work | |
- Make the JumpBack button work | |
- Make the progress bar work | |
- change the width of the inner element to the percentage of the played track | |
- add a click handler on the progress bar to jump to the clicked spot | |
Here is the audio API you'll need to use, `audio` is the <audio/> dom nod | |
instance, you can access it as `this.audio` in `AudioPlayer` | |
```js | |
// play/pause | |
audio.play() | |
audio.pause() | |
// change the current time | |
audio.currentTime = audio.currentTime + 10 | |
audio.currentTime = audio.currentTime - 30 | |
// know the duration | |
audio.duration | |
// values to calculate relative mouse click position | |
// on the progress bar | |
event.clientX // left position *from window* of mouse click | |
const rect = node.getBoundingClientRect() | |
rect.left // left position *of node from window* | |
rect.width // width of node | |
``` | |
Other notes about the `<audio/>` tag: | |
- You can't know the duration until `onLoadedData` | |
- `onTimeUpdate` is fired when the currentTime changes | |
- `onEnded` is called when the track plays through to the end and is no | |
longer playing | |
Good luck! | |
*/ | |
import "./index.css"; | |
import React from "react"; | |
// import PropTypes from "prop-types"; | |
import podcast from "./podcast.mp4"; | |
import mario from "./mariobros.mp3"; | |
import FaPause from "react-icons/lib/fa/pause"; | |
import FaPlay from "react-icons/lib/fa/play"; | |
import FaRepeat from "react-icons/lib/fa/repeat"; | |
import FaRotateLeft from "react-icons/lib/fa/rotate-left"; | |
// Polyfill for React 15 => react-create-context | |
const AudioContext = React.createContext(); | |
class AudioPlayer extends React.Component { | |
state = { | |
isPlaying: false, | |
isLoaded: false, | |
currentTime: 0, | |
duration: 0, | |
play: () => { | |
this.setState( | |
{ | |
isPlaying: true | |
}, | |
() => { | |
this.audio.play(); | |
} | |
); | |
}, | |
pause: () => { | |
this.setState( | |
{ | |
isPlaying: false | |
}, | |
() => { | |
this.audio.pause(); | |
} | |
); | |
}, | |
jumpForward: () => { | |
this.audio.currentTime = this.audio.currentTime + 30; | |
}, | |
jumpBack: () => { | |
this.audio.currentTime = this.audio.currentTime - 30; | |
}, | |
setPlayHead: event => { | |
let { left, width } = event.currentTarget.getBoundingClientRect(); | |
let percentage = (event.clientX - left) / width; | |
this.audio.currentTime = this.audio.duration * percentage; | |
} | |
}; | |
render() { | |
return ( | |
<AudioContext.Provider value={this.state}> | |
<div className="audio-player"> | |
<audio | |
src={this.props.source} | |
onTimeUpdate={() => | |
this.setState({ | |
currentTime: this.audio.currentTime | |
}) | |
} | |
onLoadedData={() => | |
this.setState({ isLoaded: true, duration: this.audio.duration }) | |
} | |
onEnded={() => this.setState({ isPlaying: false, currentTime: 0 })} | |
ref={n => (this.audio = n)} | |
/> | |
{this.props.children} | |
</div> | |
</AudioContext.Provider> | |
); | |
} | |
} | |
class Play extends React.Component { | |
render() { | |
return ( | |
<AudioContext.Consumer> | |
{context => { | |
return ( | |
<button | |
className="icon-button" | |
onClick={context.play} | |
disabled={context.isPlaying} | |
title="play" | |
> | |
<FaPlay /> | |
</button> | |
); | |
}} | |
</AudioContext.Consumer> | |
); | |
} | |
} | |
class Pause extends React.Component { | |
render() { | |
return ( | |
<AudioContext.Consumer> | |
{context => ( | |
<button | |
className="icon-button" | |
onClick={context.pause} | |
disabled={!context.isPlaying} | |
title="pause" | |
> | |
<FaPause /> | |
</button> | |
)} | |
</AudioContext.Consumer> | |
); | |
} | |
} | |
class PlayPause extends React.Component { | |
render() { | |
return ( | |
<AudioContext.Consumer> | |
{context => { | |
return !context.isPlaying ? <Play /> : <Pause />; | |
}} | |
</AudioContext.Consumer> | |
); | |
} | |
} | |
class JumpForward extends React.Component { | |
render() { | |
return ( | |
<AudioContext.Consumer> | |
{context => ( | |
<button | |
className="icon-button" | |
onClick={context.jumpForward} | |
disabled={!context.isPlaying} | |
title="Forward 10 Seconds" | |
> | |
<FaRepeat /> | |
</button> | |
)} | |
</AudioContext.Consumer> | |
); | |
} | |
} | |
class JumpBack extends React.Component { | |
render() { | |
return ( | |
<AudioContext.Consumer> | |
{context => ( | |
<button | |
className="icon-button" | |
onClick={context.jumpBack} | |
disabled={!context.isPlaying} | |
title="Back 10 Seconds" | |
> | |
<FaRotateLeft /> | |
</button> | |
)} | |
</AudioContext.Consumer> | |
); | |
} | |
} | |
class Progress extends React.Component { | |
render() { | |
return ( | |
<AudioContext.Consumer> | |
{context => ( | |
<div className="progress" onClick={context.setPlayHead}> | |
<div | |
className="progress-bar" | |
style={{ | |
width: `${ | |
context.isLoaded | |
? context.currentTime / context.duration * 100 | |
: 0 | |
}%` | |
}} | |
/> | |
</div> | |
)} | |
</AudioContext.Consumer> | |
); | |
} | |
} | |
class ProgressCounter extends React.Component { | |
formatTime = timeInSeconds => { | |
let seconds = Math.floor(timeInSeconds % 60); | |
let minutes = Math.floor(timeInSeconds / 60.0); | |
return `${Math.round(minutes) | |
.toString() | |
.padStart(2, "0")}:${Math.round(seconds) | |
.toString() | |
.padStart(2, "0")}`; | |
}; | |
render() { | |
return ( | |
<AudioContext.Consumer> | |
{context => ( | |
<div className="counter" style={{ float: "right" }}> | |
{context.isLoaded && ( | |
<React.Fragment> | |
<span className="current-time"> | |
{this.formatTime(context.currentTime)} | |
</span>{" "} | |
/ | |
<span className="total-time"> | |
{this.formatTime(context.duration)} | |
</span> | |
</React.Fragment> | |
)} | |
</div> | |
)} | |
</AudioContext.Consumer> | |
); | |
} | |
} | |
const Exercise = () => ( | |
<div className="exercise"> | |
<AudioPlayer source={mario}> | |
<PlayPause /> <span className="player-text">Mario Bros. Remix</span> | |
<Progress /> | |
<ProgressCounter /> | |
</AudioPlayer> | |
<AudioPlayer source={podcast}> | |
<PlayPause /> <JumpBack /> <JumpForward />{" "} | |
<span className="player-text">Workshop.me Podcast Episode 02</span> | |
<Progress /> | |
<ProgressCounter /> | |
</AudioPlayer> | |
</div> | |
); | |
export default Exercise; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment