patternpythondjangoMinor
Serving a growing audio file with Django
Viewed 0 times
filedjangowithservingaudiogrowing
Problem
I would like to serve a growing audio file (raw PCM audio for now) with the possibility of seeking. I tried to manage it with Apache, but in the end wasn't able to achieve what I wanted.
Now I managed to come up with a solution within Django that seems to work quite nicely. Basically I created a view that delivers a part of the file and then sends a response with status code 206 and Content-Range header.
```
### DJANGO VIEW
MAX_CHUNK_SIZE = 32000
HEADER_PATH = "/path/to/header.wav"
MAX_WAV_LENGTH = 4294967303
BYTES_PER_SECOND = 32000
def get_audio(request, file_name):
# get offset
pos = int(request.GET.get("pos", 0))
offset = pos * BYTES_PER_SECOND
# get range
r = request.META["HTTP_RANGE"]
start = int(r.replace("bytes=", "").split("-")[0])
start_o = start + offset
# in case this is the first request, a wave header is added
if start == 0:
with open(HEADER_PATH, "rb") as f:
data = f.read()
length = 44
else:
data = ""
length = 0
start_o -= 44
file_path = join(settings.STREAMS_DIR, file_name)
# wait up to 10 seconds if the end of the file is reached
# (in case it grows more)
for _ in range(10):
size = getsize(file_path)
if size - start_o > 0:
break
else:
sleep(1)
length += min(MAX_CHUNK_SIZE, size - start_o)
# if the length is zero (if end of file is reached and there is
# nothing more to server), set the total length to the actual
# served length
if length == 0:
total = size - offset + 44
# else, set the total length to the maximum possible wav length
# so that the browsers know that it has to send subsequent requests
else:
total = MAX_WAV_LENGTH
# get the actual data from the raw audio file
with open(file_path, "rb") as f:
f.seek(start_o)
data += f.read(length)
# send response with use of Content-Range
resp = HttpResponse
Now I managed to come up with a solution within Django that seems to work quite nicely. Basically I created a view that delivers a part of the file and then sends a response with status code 206 and Content-Range header.
```
### DJANGO VIEW
MAX_CHUNK_SIZE = 32000
HEADER_PATH = "/path/to/header.wav"
MAX_WAV_LENGTH = 4294967303
BYTES_PER_SECOND = 32000
def get_audio(request, file_name):
# get offset
pos = int(request.GET.get("pos", 0))
offset = pos * BYTES_PER_SECOND
# get range
r = request.META["HTTP_RANGE"]
start = int(r.replace("bytes=", "").split("-")[0])
start_o = start + offset
# in case this is the first request, a wave header is added
if start == 0:
with open(HEADER_PATH, "rb") as f:
data = f.read()
length = 44
else:
data = ""
length = 0
start_o -= 44
file_path = join(settings.STREAMS_DIR, file_name)
# wait up to 10 seconds if the end of the file is reached
# (in case it grows more)
for _ in range(10):
size = getsize(file_path)
if size - start_o > 0:
break
else:
sleep(1)
length += min(MAX_CHUNK_SIZE, size - start_o)
# if the length is zero (if end of file is reached and there is
# nothing more to server), set the total length to the actual
# served length
if length == 0:
total = size - offset + 44
# else, set the total length to the maximum possible wav length
# so that the browsers know that it has to send subsequent requests
else:
total = MAX_WAV_LENGTH
# get the actual data from the raw audio file
with open(file_path, "rb") as f:
f.seek(start_o)
data += f.read(length)
# send response with use of Content-Range
resp = HttpResponse
Solution
- For large constants consider writing them as an expression and
possibly comment on the size, just in case, to make it easier to
understand.
- I'm not particularly fond of waiting for the file to grow, but I guess
if it works it's fine.
- Serving static files is implemented in Django, including HTTP range
parsing. However
the one method
I found only works in debug mode. Still, I'd try to reuse
django.utils.http.parse_http_range anddjango.http.response.PartialHttpResponse if possible, so that notall of the data is read into memory at once.
Otherwise looks okay I'd say.
Context
StackExchange Code Review Q#108998, answer score: 2
Revisions (0)
No revisions yet.