1

How can I stream multiple videos in FastAPI? With the following approach, I can only have one request open in the browser, any other request I make is not working.

async def stream_video(url):
    """
    frames generator from video feed
    """
    global outputFrame

    while(True):
        cap = cv2.VideoCapture(url)
        ret, frame = cap.read()
        (flag, encodedImage) = cv2.imencode(".jpg", frame)
        
        if not flag:
            continue

        yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' +
               bytearray(encodedImage) + b'\r\n')


@router.get("/{camera_id}")
async def video_feed(camera_id):
    """
    return the response generated along with specific media type
    """
    if cam_list.get(camera_id) is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                            detail=f"Camera with id {camera_id} not found")

    url = cam_list[camera_id]

    return StreamingResponse(stream_video(url), media_type="multipart/x-mixed-replace;boundary=frame")

On browser, http://127.0.0.1:8000/endpoint1/camera1 works fine and I can see the camera feed, but if I open another tab and open http://127.0.0.1:8000/endpoint1/camera2, this one is not showing anything.

Chris
  • 18,724
  • 6
  • 46
  • 80
HamzaDLM
  • 50
  • 8

1 Answers1

2

async does not magically make things parallel, so if you're never awaiting anything and just feeding stuff as fast as you can (or waiting in a blocking function like cv2.VideoCapture(url)), you're going to only have a single function executing.

If you don't have the option to do things properly async, just drop the async definition on the function and FastAPI will run your code in a threadpool instead.

Another option is to use tell uvicorn to use multiple workers with the --workers parameter, but you should still aim to use proper async/await compatible calls in that case - but I'm not sure that opencv have a proper async API available.

MatsLindh
  • 49,529
  • 4
  • 53
  • 84
  • 2
    That was my first thought as well, as previously explained in [this answer](https://stackoverflow.com/a/71517830/17865804). However, running some code from a [related answer](https://stackoverflow.com/a/70626324/17865804) (in which for some kind of reason I used `async def` - most likely because this is how [`StreamingResponse`](https://fastapi.tiangolo.com/advanced/custom-response/#streamingresponse) was documented), it worked as expected (i.e., was able to access the streaming from multiple clients/tabs in the browser). 1/2 – Chris Apr 16 '22 at 20:12
  • 2
    As it turns out, `StreamingResponse` **does** run the code (for sending the body chunks) in a threadpool (see [here](https://github.com/encode/starlette/blob/master/starlette/responses.py#L223)), using [`iterate_in_threadpool`](https://github.com/encode/starlette/blob/master/starlette/concurrency.py#L58). Hence, OP's issue most likely lies elsewhere. Without a [mre], as well as any error logs and/or browser response data when accessing `camera2`, would be hard to tell where that might be. Also, they shouldn't call `cv2.VideoCapture(url)` inside the `while` loop, but rather outside of it. 2/2 – Chris Apr 16 '22 at 20:12