Search code examples
reactjstypescripthtml5-videomediastreamweb-mediarecorder

<video> element receives as source a MediaStream, even although its src property is set to an object URL string


I'm trying to record video in a React + TS component. I want to conditionally render the video player: if the user is recording, the video player source should be the live stream (MediaStream), but if the recording is finished, the video player source should change from the live stream to the newly created recordingURL (created with URL.createObjectURL(blob).

As you can see, I'm replacing the one <video/> tag with another, because I cannot just use HTMLMediaElement.srcObject to set a source different from a MediaStream.

<div className="VideoRecorder flex flex-col mx-2">
    {recordingURL.current! ? (
         <video
             src={recordingURL.current}
             playsInline
             controls
             className="RECORDED"
         />
     ) : (
         <video
             ref={(ref) => {
                  if (ref)
                      ref.srcObject = recordingStream.current;
             }}
             autoPlay
             className="LIVE"
         />
     )}
</div>

When the recording starts, the video player successfully displays the live MediaStream. When the recording stops the video player does change (I checked the render HTML and the video player with the className="RECORDED" comes in replacement for the one with the className="LIVE"), the recordingURL.current also changes from null to the object URL string but the problem is that, somehow, this new video player is still showing a live stream

The code below shows the relevant state and ref managing for the MediaRecorder.

const recordingURL = useRef<null | string>(null);

const stopRecording = () => {
    const blob = new Blob(recordedChunks.current, {
        type: mediaConstraints.audio ? 'audio/mpeg' : 'video/mp4'
    });
    setIsRecording(false);

    recordingURL.current = URL.createObjectURL(blob);
    setBlob(blob);
};

const initializeDevice = async () => {
    try {
        recordingStream.current = await navigator.mediaDevices.getUserMedia(
            mediaConstraints
        );

        setPermissionGranted(true);

        mediaRecorder.current = new MediaRecorder(recordingStream.current);
        mediaRecorder.current!.ondataavailable = saveRecordingChunks;
        mediaRecorder.current!.onstop = stopRecording;
    } catch (error: any) {
        // Exception handling...
    }
};

And this HTML fragment shows the <video> element that replaced the recording one. As you can see, its src is set to a url, yet this component actually shows the data stream:

<video class="RECORDED" src="blob:http://localhost:3000/af437ac7-e77e-4d5b-a2f5-2645507448ed" playsinline="" controls=""></video>

What could be causing this problem?


Solution

  • Adding a key parameter to each <video> element solved the problem.