Reputation: 1061
I am trying to create a server (loosely) based on an old blog post to stream video with Quart.
To stream video to a client, it seems all I should need to do is have a route that returns a generator of frames. However, actually doing this results in a constant repeated message of socket.send() raised exception
, and shows a broken image on the client. After that, the server does not appear to respond to further requests.
Using more inspiration from the original post, I tried returning a Response
(using return Response(generator, mimetype="multipart/x-mixed-replace; boundary=frame")
.) This does actually display video on the client, but as soon as they disconnect (close the tab, navigate to another page, etc) the server begins spamming socket.send() raised exception
again and does not respond to further requests.
My code is below.
# in app.py
from camera_opencv import Camera
import os
from quart import (
Quart,
render_template,
Response,
send_from_directory,
)
app = Quart(__name__)
async def gen(c: Camera):
for frame in c.frames():
# d_frame = cv_processing.draw_debugs_jpegs(c.get_frame()[1])
yield (b"--frame\r\nContent-Type: image/jpeg\r\n\r\n" + frame[0] + b"\r\n")
c_gen = gen(Camera(0))
@app.route("/video_feed")
async def feed():
"""Streaming route (img src)"""
# return c_gen
return Response(c_gen, mimetype="multipart/x-mixed-replace; boundary=frame")
# in camera_opencv.py
from asyncio import Event
import cv2
class Camera:
last_frame = []
def __init__(self, source: int):
self.video_source = source
self.cv2_cam = cv2.VideoCapture(self.video_source)
self.event = Event()
def set_video_source(self, source):
self.video_source = source
self.cv2_cam = cv2.VideoCapture(self.video_source)
async def get_frame(self):
await self.event.wait()
self.event.clear()
return Camera.last_frame
def frames(self):
if not self.cv2_cam.isOpened():
raise RuntimeError("Could not start camera.")
while True:
# read current frame
_, img = self.cv2_cam.read()
# encode as a jpeg image and return it
Camera.last_frame = [cv2.imencode(".jpg", img)[1].tobytes(), img]
self.event.set()
yield Camera.last_frame
self.cv2_cam.release()
Upvotes: 2
Views: 1284
Reputation: 1061
This was originally an issue with Quart itself.
After a round of bugfixes to both Quart and Hypercorn, the code as posted functions as intended (as of 2018-11-13.)
Upvotes: 1