Build a Fullstack Music App with Next.js requestAnimationFrame & Synching UI
A second version of this course released using Next 13+, but this course is great if you want to see another example fullstack project. We now recommend you take the Build a Fullstack App with Next.js, v2 course.
Transcript from the "requestAnimationFrame & Synching UI" Lesson
>> So now we gotta find a way to update our UI as we change songs and as a song continues to play cuz right now it's just static, it's not doing anything. Although it's functional it doesn't give the user any feedback on information so we need to do that.
[00:00:17] So the best way to do that is to find some way to watch a certain value and then on some interval rerender. If you ever heard of requestAnimationFrame that's what we have to use. So requestAnimationFrame, you can think of it as like an interval that when used, it's basically asking the browser like, hey, can you call me before you paint first?
[00:00:47] And the browser's like, sure, I'll call you first. So that guarantees that it's smooth is usually called 60 times per second or up to however faster screen is. If you have 120 hertz screen it might be called 120 times a second, but at least 60 times a second.
[00:01:03] And it also handles conflicts, if you have more than one thing that's trying to animate, it'll bash them and do them together so everything's nice and smooth and it happens before the paint. So some people like, Just do set interval up to 60 times a second. No, that doesn't do the same thing that's running it in a different thread so, you wanna do the requestAnimationFrame.
[00:01:26] So we need to do that on some type of interval basically what the requestAnimationFrame is but we wanna do it only while playing is true and we need to also account for when you're seeking. So if you're dragging we don't wanna do it, like imagine yourself dragging and then it's also moving at the same time fighting your drag we don't wanna do that.
[00:01:51] So in order to know when that's happening we need to use effects. So we can do a useEffect to watch whether playing is true or false or whether its seeking is true or false. And then we can determine whether or not we should be animating this value or not animating this value.
[00:02:07] So to do that, we'll come up here we'll make a useEffects. We'll say useEffects, that takes a callback. This useEffect is going to be interested in two things, it's going to be interested in playing state and its seeking state. Just like I said, so we only wanna animate as we're playing, not when we're paused and when we're not seeking.
[00:02:34] And we also wanna be able to cancel the AnimationFrame just like you would cancel a set timeout or set interval you have to cancel the AnimationFrame as well. Otherwise you might have memory leaks and for whatever reasons you just don't wanna do that. So just like a set interval set timeout requestAnimationFrame will return an ID that you can use to cancel later on.
[00:02:54] So we're gonna make a reference to the ID and we'll call it a timer Id. And then I'm gonna say if you are playing, or if the player is playing and it is not seeking as in someone is not dragging the thumbnail then I wanna go ahead and make a function that we're gonna use for our requestAnimationFrames.
[00:03:17] I was gonna say, it's gonna make this function here. And I'm gonna to say as this thing is animating, I wanna setSeek cuz remember calling setSeek is what's gonna update the seek value in our UI because our UI seek value is bound to the seek state. So by setting the seek, we are gonna be updating it.
[00:03:38] So we wanna set that to whatever the soundRef.current seek value is. So whatever the current value of the song is which you saw me clicking around it's working it does seek it's functional. So the rack taller does know the current seek value so now we're binding that to the seek state that we're keeping track of which is bound to the UI of the seek bar.
[00:04:00] So we're just keeping those in sync basically. And then we're gonna say timer Id is gonna equal a requestAnimationFrame like this, and we're gonna pass in our function like that. So this too is also recursive every time you do requestAnimationFrame is gonna be recursive almost every time. And then now we can come down here and we can say timer Id equals requestAnimationFrame pass it in, so this will kick it off.
[00:04:43] And then now all we have to do is set a return value. When you return something inside of a UseEffects this function gets called when the component that's using the useEffect gets unmounted from the DOM. This is where we're gonna clean up the requestAnimationFrame. So if you're using something like a WebSocket, or set interval, this is where you would clean these things up.
[00:05:02] So we're gonna go to clean up that and we can use cancel AnimationFrame and then pass it a timer Id. And if our conditional was initially false, and we end up doing none of that, then I just wanna cancel the AnimationFrame as well with a timer Id. So just to walk through this again, we're gonna set up a useEffect that tracks the playing states and its seeking states.
[00:05:36] If the music is currently playing, and the user is not currently seeking, I wanna request an AnimationFrame which think of it as a set interval, so it's basically gonna call this function right here that I called F. It's gonna call it 60 times a second. And every time it's called, it's updating the UI, the seek to whatever the sounds current seek is.
[00:05:57] So it'll be ticking and it'll be smooth because it's calling it 60 times a second, which is 60 frames per second. So to look smooth, that's the minimum you need as a human for something to seek smooth. So it might go higher depending on your refresh rate of your computer, like on the new MacBook that have like promotion inside of Safari that might even look faster, cuz those things can go up to 120 Hertz.
[00:06:20] So you might see 120 frames per second per animation on a play. And then what we do is then we kick it off. This is a initial call. So we recall requestAnimationFrame, it takes our function, one take goes by it sets the seek, and then it calls itself again, it sets the seek, and then it calls itself again.
[00:06:42] And it does that over and over and over until we say cancel. And cancel happens when a, we unmount this component as in we route away, which in our case never happens cuz this player bar is always on a page unless you're on the sign in or signup page that would be the only time.
[00:06:57] Or b, this conditional was false which would mean you either paused or you started seeking, so that's when it will stop. Which is the only time you would see the SeekBar not move is if it was paused or you were dragging it yourself, that's it. So that's what this is doing.
[00:07:14] I know it'll look harder than it is but then when you think about it it's like yeah, that's what it would do, that's how it'll work. So let's go check it out let's see what we broke, I'm gonna refresh this, click a button here.
[00:07:34] We got a question, Mark?
>> What about song end?
>> We'll get the song end, good question. I'm saving that, that's where the gotcha is. I was gonna see if anyone catches it. But as you can see my seek is actually updating, it's moving, it's really nice, it's totally working.
[00:07:53] When I hit pause it stops, because I use effects and that conditional playing is false so it cancels the AnimationFrame which stops setting the seek. But when I hit play again, the useEffect fires again cuz it's watching the playing state, now the conditional passes and a new AnimationFrame is started.
[00:08:12] All right, and then if I start seeking, you can see it doesn't move this is just me seeking. You can even hear it scrubbing. [SOUND] So that works, so we are good there. So that logic is working, the next thing we need to do is need to update this count right here.
[00:08:29] It seems to just be stuck at whatever hard coded value that I have it just says 121. So let us update that to be the current seek value. So let's go back to our code and that's gonna be basically to the left of our range. So if we go down to where we at this, That's why I didn't find it was collapsed that's why I missed it.
[00:08:51] I was like, where's this thing even at? So to do that, we really just wanna get the seek value, and it is format the time. So we'll just come here, we'll get rid of that, we'll use that format time, helper that we made, and we'll just pass and seek.
[00:09:07] So now the UI in this text is now bound to whatever seek value that the song is playing through Howler and that's all happening because of that useEffect that we wrote up here.