3
\$\begingroup\$

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(data, content_type="audio/x-wav", status=206)
 resp["Content-Range"] = "bytes " + str(start) + "-" + str(start + length) + "/" + str(total)
 return resp

In the browser, I can then simply request my audio like in the following way, to make it play from any point:

var fileName = "xyz.wav";
var start = 25; //seconds
audio = new Audio();
audio.src = "/getAudio/" + fileName + "?pos=" + start;
audio.play();

As I said, this all seems to work pretty well. But I would like to know this is a valid approach that I can use in production or if there are some major problems that I am not aware of.

asked Oct 28, 2015 at 11:24
\$\endgroup\$
3
  • \$\begingroup\$ The one issue would be performance. Unless the sample is quite small I'd say you shouldn't send raw PCM anyway. Have you looked at streaming media servers (Icecast and friends), that would be what you want for production IMO. \$\endgroup\$ Commented Oct 28, 2015 at 15:30
  • 1
    \$\begingroup\$ @ferada I'm using wave files for now, just to test the basics. Later I would indeed switch to something like mp3 or ogg. I must admit that I didn't look too much into icecast until now but I was under the impression that it's purpose is live streaming and not so much streaming of static files (especially if they are growing in size). But I will check it out. \$\endgroup\$ Commented Oct 29, 2015 at 13:43
  • \$\begingroup\$ Right, the "growing file" part lead me to believe that it would fit. Nevermind. \$\endgroup\$ Commented Oct 29, 2015 at 14:12

1 Answer 1

2
\$\begingroup\$
  • 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 and django.http.response.PartialHttpResponse if possible, so that not all of the data is read into memory at once.

Otherwise looks okay I'd say.

answered Oct 29, 2015 at 14:56
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.