reactjsreact-hookshtml5-video

UseEffect and video element not working as expected


I have a video element that has a ref to use to get it's properties. Pretty standard stuff, I think. The problem is when the effect runs, the video element (in videoRef.current) gets logged and has a duration property of 311, but beneath it where I try to log the duration property itself doesn't work. It logs NaN.

Here's the code relevant to the question.

const Home = () => {
    const [videoState, setVideoState] = useState({
        playing: false,
        currentTime: 0,
        volume: "50%",
        playbackSpeed: 1,
        totalTime: 0,
    })
    const videoRef = useRef<HTMLVideoElement>(null)

    

    useEffect(() => {
        if (videoRef.current) {
            console.dir(videoRef.current)
            console.log(+videoRef.current.duration)
            // console.log(typeof(videoRef.current.duration))
            // @ts-ignore
            setVideoState((prev) => ({ ...prev, totalTime: videoRef.current.duration }))
        }
    }, [])

    return (
        <div className='p-8 bg-red-500 w-full h-full relative'>
            <div className='relative w-fit h-fit'>
                <video
                    src={fall}
                    ref={videoRef}
                    // onPlaying={handleTimeUpdate}
                    onTimeUpdate={handleTimeUpdate}
                ></video>
        </div>
    )
}
export default Home

the output

enter image description here

and in the video object, there's a figure for duration enter image description here

I don't get why duration in video object is 311 when logged out but it's NaN when it's logged directly.

I expected the second log to log the duration figures.


Solution

  • You can look at this question => Retrieving HTML5 video duration separately from the file

    The issue is that the duration property is available after the metadata has been loaded. You need to wait for it.

    try with this useEffect

    useEffect(() => {
            const handleDurationChange = () => {
                if (videoRef.current) {
                    console.dir(videoRef.current);
                    console.log(videoRef.current.duration);
                    setVideoState((prev) => ({ ...prev, totalTime: videoRef.current.duration }));
                }
            };
    
            if (videoRef.current) {
                videoRef.current.addEventListener('durationchange', handleDurationChange);
            }
    
            return () => {
                if (videoRef.current) {
                    videoRef.current.removeEventListener('durationchange', handleDurationChange);
                }
            };
        }, []);
    

    To understand, you can copy/paste this snippet in your browser console:

    const obj = { a: 1, b: 2 };
    console.dir(obj); // => you will see { a: 3, b: 2 } in the browser
    console.dir({ ...obj }); // => you will see { a: 1, b: 2 } in the browser
    obj.a = 3;
    

    when you pass directly the object the browser kept a reference of it, and if he changes it will show the object mutated (with the new state)

    If you want to "capture" the state at the moment of the console.dir you need to pass a copy of the object.