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.

\$\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\$
    – ferada
    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\$
    – basilikum
    Oct 29, 2015 at 13:43
  • \$\begingroup\$ Right, the "growing file" part lead me to believe that it would fit. Nevermind. \$\endgroup\$
    – ferada
    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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.