4

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?

UberStreuneR
  • 57
  • 2
  • 6
  • SO is useful to all of us, as no doubt it has served you in the past, not because an answer serves _you_ and _right now_, but rather because it serves the very community that grows it. In this sense, the repository where you seem to have hosted your initial question code — https://github.com/UberStreuneR/fastapi-video — is sadly a 404, meaning that after having extracted your use of this, you went on and decided to drop it from public. You have 12 repos and no apparent lack of Github space. Kindly restore it or push your full code here. – Ricardo Nov 12 '22 at 14:11
  • Thanks for reminding me, I restored the repo. The topic issue is exhausted since it requires more advanced file streaming technologies (MPD file generation, JS video player, etc.) to be implemented. – UberStreuneR Nov 13 '22 at 11:47
  • Your example worked for me without doing anything, which I am glad for because I came here with the question of how to do this. Thank you for the question and the answer! – systematical Aug 06 '23 at 15:57

2 Answers2

2

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
lsabi
  • 3,641
  • 1
  • 14
  • 26
  • Your response certainly is helpful, however timeline manipulation and division by chunks is at a crux of the question, and neither option does both. Perhaps it's a Javascript problem. – UberStreuneR Jul 10 '22 at 05:56
  • The problem lied for sure in the parameters. I got a `500 Internal Server Error` when running your code. That being said, it's critical, but you need then to create an appropriate response that I don't know of. Maybe a javascript solution works well enough https://stackoverflow.com/questions/5981427/start-html5-video-at-a-particular-position-when-loading – lsabi Jul 10 '22 at 13:16
  • You're right. Digging into the problem, it's obvious that what I wanted is essentially a video player, and there is a lot of things that constitute it, to name a few: Media Source Extensions and Media Presentation Description files, as well as splitting the video into chunks on the server. It's not so trivial as just to fix the Response function. – UberStreuneR Jul 10 '22 at 15:07
0

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.

JarroVGIT
  • 4,291
  • 1
  • 17
  • 29