Closed rchrdnsh closed 4 years ago
Have you run into any troubles, or are you wondering about hooks in general?
well, i have a music player for a Gatsby site, with everything built and working fine, except the progress slider and volume control...the player is based upon this article:
https://upmostly.com/tutorials/how-to-use-the-usecontext-hook-in-react
I have even managed how to figure out how to add the currentTime
and duration
as well, and I am now stuck on how to add a progress bar/ slider, which updates based on the progress of the current song, and then also allows the user to jump to any point in the song and the playback updates to that point.
The example is using context and hooks, with a custom useMusicPlayer
hook, like so:
const useMusicPlayer = () => {
const [state, setState] = useContext(MusicPlayerContext)
// query all mp3 and jpg files from /content/music/
const assets = useStaticQuery(graphql`
query Assets {
allFile(
filter: {
extension: { in: ["mp3", "jpg"] }
absolutePath: { regex: "/content/music/" }
}
) {
edges {
node {
publicURL
relativePath
}
}
}
}
`)
// convert to obj for fast lookup
const assetObj = useMemo(
() =>
assets.allFile.edges.reduce((obj, file) => {
const { publicURL, relativePath } = file.node
obj[relativePath] = publicURL
return obj
}, {}),
[assets]
)
// Play a specific track
function playTrack(index) {
if (index === state.currentTrackIndex) {
togglePlay()
} else {
state.audioPlayer.pause()
const base = state.tracks[index].audio.base // frost.mp3
const baseName = basename(base) // frost
// new Audio() does not support relative path
// hence the need for window.location.origin
const audioPlayer = new Audio(
`${window.location.origin}${assetObj[`${baseName}/${base}`]}`
) // new Audio('http://www.domain.com/static/frost-[hash].mp3')
audioPlayer.play()
setState(state => ({
...state,
currentTrackIndex: index,
isPlaying: true,
audioPlayer,
}))
}
}
// Toggle play or pause
function togglePlay() {
if (state.isPlaying) {
state.audioPlayer.pause()
} else {
state.audioPlayer.play()
}
setState(state => ({ ...state, isPlaying: !state.isPlaying }))
}
// Play the previous track in the tracks array
function playPreviousTrack() {
const newIndex =
(((state.currentTrackIndex + -1) % state.tracks.length) +
state.tracks.length) %
state.tracks.length
playTrack(newIndex)
}
// Play the next track in the tracks array
function playNextTrack() {
const newIndex = (state.currentTrackIndex + 1) % state.tracks.length
playTrack(newIndex)
}
// Transform the currentTime info into minutes and seconds
function getTime(time) {
if(!isNaN(time)) {
return Math.floor(time / 60) + ':' + ('0' + Math.floor(time % 60)).slice(-2)
}
}
const [currentTime, setCurrentTime] = useState(state.audioPlayer.currentTime)
// both formattedTime and progress are states derived from audioPlayer.currentTime
// so no need another useState
const formattedTime = getTime(currentTime)
const progress = currentTime / state.audioPlayer.duration
useEffect(() => {
const timeoutId= setInterval(() => {
setCurrentTime(getTime(state.audioPlayer.currentTime))
// setCurrentTime(formattedTime)
}, 1000)
return () => {
// clean up function run when state.audioPlayer changes
// reset currentTime to 0
setCurrentTime(0)
clearInterval(timeoutId)
}
}, [state.audioPlayer]);
// get and display the duration of the track, in minutes and seconds
// let currentDuration = state.audioPlayer.duration
// let currentDuration = formatSecondsAsTime(Math.floor(state.audioPlayer.duration).toString())
const duration = getTime(state.audioPlayer.duration)
// jump to anywhere on the track using a progress bar style interface
// adjust the volume of the track
const volume = state.audioPlayer.duration.volume
let currentTrackArtwork, currentTrackAudio
if (state.currentTrackIndex !== null) {
const base = state.tracks[state.currentTrackIndex].audio.base // frost.mp3
const baseName = basename(base) // frost
currentTrackArtwork =
assetObj[
`${baseName}/${state.tracks[state.currentTrackIndex].artwork.base}`
] // assetObj['frost/frost.jpg']
currentTrackAudio =
assetObj[
`${baseName}/${state.tracks[state.currentTrackIndex].audio.base}`
] // assetObj['frost/frost.mp3']
}
return {
playTrack,
togglePlay,
currentTrackName:
state.currentTrackIndex !== null &&
state.tracks[state.currentTrackIndex].name,
currentTrackArtist:
state.currentTrackIndex !== null &&
state.tracks[state.currentTrackIndex].artist,
currentTrackArtwork,
currentTrackAudio,
currentTime,
duration,
formattedTime,
progress,
volume,
trackList: state.tracks,
isPlaying: state.isPlaying,
playPreviousTrack,
playNextTrack,
}
}
export default useMusicPlayer
and then I import these things into a PlayerControls.js
file, like so:
const Controls = () => {
const {
isPlaying,
currentTrackName,
currentTrackArtwork,
currentTime,
duration,
formattedTime,
progress,
volume,
togglePlay,
playPreviousTrack,
playNextTrack
} = useMusicPlayer()
return (
<>
<FlexStart>
<TrackArtwork src={currentTrackArtwork} alt="Album Artwork." />
<TrackName>{currentTrackName}</TrackName>
</FlexStart>
<FlexCenter>
<Button onClick={playPreviousTrack} disabled={!currentTrackName}>
<Image src={previousButton} alt="Previous Button"/>
</Button>
<Button onClick={togglePlay} disabled={!currentTrackName}>
{
isPlaying ?
<Image src={pauseButton} alt="Pause Button"/>
:
<Image src={playButton} alt="Play Button"/>
}
</Button>
<Button onClick={playNextTrack} disabled={!currentTrackName}>
<Image src={nextButton} alt="Next Button"/>
</Button>
</FlexCenter>
<FlexCenter>
<TrackDuration>{currentTime}</TrackDuration>
// Progress Slider would go here...
<TrackDuration>{duration}</TrackDuration>
// Volume Slider would go here
</FlexCenter>
</>
)
}
export default Controls
...so I'm looking at all the props with this
in them and I don't really know what to do, or how to integrate your slider with what I have so far...
I know it's a bunch of code, but what a bunch of it is also doing is grabbing a bunch of data from some mdx files and creating the tracklist out of that mdx frontmatter and also grabbing the artworks and audio files as well, adding that all in to context.
If you are curious to look at the repo:
https://github.com/rchrdnsh/RYKR
I hope this makes sense, what I am stuck on. I just see a bunch of this.something, and I am wondering what I would need to do to adopt this component to what I have already (with much help from very smart people!) have built :-)
Well, generally what you need to do is manage your state somehow and then pass this state on as props to the <Slider />
or whichever component you need. Have you looked at the Progress bar with buffer recipe in the README? The <Slider />
is really just an invisible wrapper that provides mouse interaction helpers. What you want to render inside it is really up to you.
It sounds like you're fighting with hooks, and if you're new to them I would strongly recommend building something easier to begin with!
I have an app that is built with context and hooks, and i am curious wether i could used the progress slider with hooks, and useState, and if so, what would need to change...not quite sure how it works, really.