Use the <video>
tag of html5 to play:
<video width="320" height="240" controls>
<source src="/static/video/demo.mp4" type="video/mp4">
Your browser does not support the Video tag.
</video>
But in this way, the progress bar in the video cannot be used, and if it is returned as a static file, the background program will take up a lot of memory.
The use of response streams can solve these two problems.
Most Django responses use HttpResponse
. This means that the body of the response is built in memory and sent to the HTTP client as a single piece. If you use StreamingHttpResponse
, you can return it in chunks (partial chunks). A very simple example is:
from django.http import StreamingHttpResponse
def hello():
yield 'Hello,'
yield 'there!'
def test(request):
return StreamingHttpResponse(hello)
According to the WSGI protocol, when the server is called, the application object must return an iterable, resulting in zero or more byte strings. Therefore, we can complete the flow response function by providing a generator to the server.
The common use of StreamingHttpResponse
is the downloading of some large files, etc., which can also be used to complete the function of resumable upload.
Video streaming
When using a video stream, you can get the starting number of bytes from the request header.
This field seems to be automatically provided by the browser, because in the html code, I only need to change the src
of the video from a static address to a routing method. For the response body, a range of blocks returned by the response body should also be provided:
Content-Range
respectively represents the start byte number-the end byte number / the total bytes of the file, and the content of the response body contains the content within the range of the file. The code to process the video stream is as follows:import re
import os
from wsgiref.util import FileWrapper
from django.http import StreamingHttpResponse
def file_iterator(file_name, chunk_size=8192, offset=0, length=None):
with open(file_name, "rb") as f:
f.seek(offset, os.SEEK_SET)
remaining = length
while True:
bytes_length = chunk_size if remaining is None else min(remaining, chunk_size)
data = f.read(bytes_length)
if not data:
break
if remaining:
remaining -= len(data)
yield data
def stream_video(request, path):
"""Responding to the video file by streaming media"""
range_header = request.META.get('HTTP_RANGE', '').strip()
range_re = re.compile(r'bytes\s*=\s*(\d+)\s*-\s*(\d*)', re.I)
range_match = range_re.match(range_header)
size = os.path.getsize(path)
content_type, encoding = mimetypes.guess_type(path)
content_type = content_type or 'application/octet-stream'
if range_match:
first_byte, last_byte = range_match.groups()
first_byte = int(first_byte) if first_byte else 0
last_byte = first_byte + 1024 * 1024 * 8 # 8M Each piece, the maximum volume of the response body
if last_byte >= size:
last_byte = size - 1
length = last_byte - first_byte + 1
resp = StreamingHttpResponse(file_iterator(path, offset=first_byte, length=length), status=206, content_type=content_type)
resp['Content-Length'] = str(length)
resp['Content-Range'] = 'bytes %s-%s/%s' % (first_byte, last_byte, size)
else:
# When it is not obtained by video stream, the entire file is returned by generator to save memory
resp = StreamingHttpResponse(FileWrapper(open(path, 'rb')), content_type=content_type)
resp['Content-Length'] = str(size)
resp['Accept-Ranges'] = 'bytes'
return resp