How to stream local video to browser in FastAPI?

Viewed 170

I have this minimal reproducible example here, and I can't get it to work. I tried returning all of this:

  1. Response with just data and media_type of "video/mp4" as well as "multipart/x-mixed-replace"
  2. StreamingResponse with data inside BytesIO with either of "video/mp4" and "multipart/x-mixed-replace" media_type.

At best it does two requests that return <1MB of data and a 206 status. However, making a query to /video with bytes={start}-{end} seems to work properly. What am I missing here? Am I getting it right that the video tag in index.html should automatically do requests to its source with the necessary headers?

2 Answers

The problem in your code lies in the path parameters start and end. This was creating the problem when reading the parameters from the Header. This caused an 500 Internal Server Error exception, which was not visible, unless navigating to /video endpoint.

Here's two possible solutions, taken from the docs https://fastapi.tiangolo.com/advanced/custom-response/?h=streamingre#using-streamingresponse-with-file-like-objects

Option 1

FileResponse

@app.get("/video")
async def video_endpoint():
    return FileResponse(video_path)

Option 2

StreamingResponse

@app.get("/video")
async def video_endpoint():
    def iterfile():
        with open(video_path, mode="rb") as file_like:
            yield from file_like

    return StreamingResponse(iterfile(), media_type="video/mp4")

This will allow you to stream the video without getting a headache of handling single chunks.

Option 2 is the suggested one, as this will asynchronously stream the video the client's browser. This will allow to do automatically the start end parameters instead of controlling them yourself.

Nevertheless, if you still want to specify a start and an end, you can create the endpoint with the following parameters. Reading and returning the partial file is not a simple task, because some information may be at the beginning of the file or at the end (depending on the data format). that I'm not sure how to do (nor sure if it's possible with fastapi)

@app.get("/video")
async def video_endpoint(start: int = None, end: int = None):
    if start and end:
        if end - start < 0:
            return
    else:
        if start is None:
            start = 0
        end = start + CHUNK_SIZE
    # Read and return the file

Change the return line to this:

        return Response(data, status_code=206, headers=headers, media_type="video/mp4")

StreamingResponse is not for this use case. It takes a file-handler and sends bytes as long as the client hasn't disconnected. This is handy when dealing with large files. It will keep on pushing new bits of data to the requestor.

The response for video requires that the browser actually initiated the request for the next bit of data. The first request is from byte 0 to byte N, where N is determined by the server itself. Subsequent requests will include byte N to byte Y, and so the server will send a new response with that data. The browser is responsible for retrieving it, it isn't just sent.

I noticed you state that you tried that already, but it is working on my end. Probably because I don't wrap the data in and IObyte object.

Related