reactjsimagemjpeg

'multipart/x-mixed-replace' http response not closed when no longer required


We are showing a mjpeg video stream using the html img tag. For this we have multiple implementations depending on the provider of the stream.

Streaming video is working fine. Switching streams is fine. But switching to a stream of a different type is not fine. At this point the current stream is not closed and a second stream is opened. After several such switches no more http requests can be made and the browser tab freezes.

I am able to reproduce it with this code:

interface StreamProps {
    mjpegUrl: string;
}

export const StreamType1: React.FC<StreamProps> = ({ mjpegUrl }) => {
    return <img src={mjpegUrl} />;
};

export const StreamType2: React.FC<StreamProps> = ({ mjpegUrl }) => {
    return <img src={mjpegUrl} />;
};

interface Props {
}

export const VideoStream: React.FC<Props> = () => {
  const [toggle, setToggle] = useState(true);
  
  return (
    <div>
      <button onClick={() => setToggle((current) => !current)}>
        Switch!
      </button>
      {toggle
        ? <StreamType1 mjpegUrl="http://localhost/stream1" /> 
        : <StreamType2 mjpegUrl="http://localhost/stream2" />}
    </div>
  );
};

One thing to note is that if I were to use the same stream type for both this problem does not occur. Similar as to updating the mjpegUrl property.

      {toggle
        ? <StreamType1 mjpegUrl="http://localhost/stream1" /> 
        : <StreamType1 mjpegUrl="http://localhost/stream2" />}

Why is this happening and how can it be fixed?


Solution

  • I think I figured it out.

    Updating the src property of an img will stop the current request and send a new request. This is why updating the same component works without a problem.

    When a different component of the same type is used React is smart enough to reuse the existing component. This means that the src property is updated and the request stopped.

    When a different component of a different type is used React cannot reuse the component. Therefor the src property of the img is not updated and the current request isn't stopped.

    To fix this it is possible to use a 2 step update. First update the existing component and set an empty string. And then update to the new component and value. Difficulty is when you have a deep tree of components and one of them gets replaced or removed you still have an open connection.

    In the end I based my solution on https://stackoverflow.com/a/52328525/9271844.

    const [frame, setFrame] = useState("");
    
    useEffect(() => {
      const controller = new AbortController();
      fetch(url, { signal: controller.signal })
        .then(response => { 
          // Read stream and store images in 'setFrame' using URL.createObjectURL.
          // Don't forget to call URL.revokeObjectURL when image is no longer needed.
        });
      
      return () => controller.abort();
    });
    
    return <img src={frame} />
    

    edit: This did fix the issue but it turned out that the performance suffered a lot by doing this. Decided to put the img in an iframe using https://github.com/ryanseddon/react-frame-component and that solved both issues.