remotion-dev / remotion

🎥 Make videos programmatically with React
https://remotion.dev
Other
20.96k stars 1.06k forks source link

Get access to `currentFrame`, `isPlaying` and other variables at the `<Player/>`'s level *reactively* #1532

Closed Just-Moh-it closed 1 year ago

Just-Moh-it commented 1 year ago

I'm currently implementing a video editor in remotion. It has custom playback controls too! Like play/pause, current position in playback (like 01:24:02/16:20:00), etc.

But searching through the docs, to get the current frame or isPlaying status, there are functions which could be called, like playerRef.isPlaying() or playerRef.getCurrentFrame().

This is fine for functions, but since I need to display the text reactively (like <div>{Math.ceil(currentFrame / 60)}:{currentFrame % 60}</div>), I need to get access to the currentFrame variable reactively.

What I've tried

In the composition itself, I could get currentFrame from useCurrentFrame(). I could then update a state/store shared across the parent of <Player> and the composition itself and access currentFrame on the <Player> level that way. This is too hack-y though imo.

I believe there must be a better way to get access to the currentFrame variable at the level.

aaronnuu commented 1 year ago

If you need the exact current frame while the video is playing you should listen to the frameupdate event and update your state in the callback.

function CustomPlayer () {
  const playerRef = useRef(null);
  const [currentFrame, setCurrentFrame] = useState(0);

  useEffect(() => {
    function updateCurrentFrame (e) {
        setCurrentFrame(e.detail.frame)
    }

    if (playerRef.current) {
      playerRef.current.addEventListener('frameupdate', updateCurrentFrame)
      return () => {
        playerRef.current.removeEventListener('frameupdate', updateCurrentFrame)
      }
    }
  }, [])

  return (
    <div>
      <div>{Math.ceil(currentFrame / 60)}:{currentFrame % 60}</div>
      <Player
        {...playerProps}
        ref={playerRef}
      />
    </div>
  )
}

I wouldn't recommend that you do this within any heavy components since it will now re-render a lot - if it's just wrapping the player more or less it should be fine, since that updates once per frame anyway.

I would work out your minimum resolution for what you're displaying (once per second in your case) and then only actually update the state once every 60 frames

Just-Moh-it commented 1 year ago

Oh, this is what I was looking for. Can't believe I missed it in the docs... Not the ideal solution for the long run, though I could make it work with performance compromises.

Since I'm creating the video-editor which has custom layers too, which depend on key framing, and consecutively, the currentFrame. It seems like updating the state 60 times every second while having a large DOM might not be the best way.

Would have been great if the reactive variables were made available (through a hook maybe?), since this is the react-way of doing it and would require little probing through state variables and event listeners. @JonnyBurger

Thanks for the solution though 😊!

JonnyBurger commented 1 year ago

I think the model of getting the time update via an event listener is correct, because we think otherwise it is way too easy to make a mistake where your whole app re-renders too often.

I wrote an article now based on this issue that gives a code sample about how I think it is solved best! https://www.remotion.dev/docs/player/current-time