Record Video (+ Audio) using WebRTC and upload to Django

3 Years ago I made a short post about how to Record Audio in the Browser and store the result on a the server using Django. Recently, I have updated the used library and also supported Video Recording. Luckily, I didn’t really have to change the Backend at all, nor the upload function. I am using RecordRTC which provides a lot of demos. To get a good understanding on what is essentially required to record video (and / or audio) check out the simple demos. You can just download or clone the whole github repository and run these demos locally in your browser without any server / backend whatsoever.

Handling video instead of audio is very similar, however due to its larger size, I had to modify the upload function a little, to make the request into a multiform request:

function upload(blob) {
            var formData = new FormData();
            formData.append("blob", blob, 'myfile');
            var xhr = new XMLHttpRequest();
            xhr.open('POST', '{% url 'recorder:upload' %}', true);
            xhr.setRequestHeader("X-CSRFToken", "{{ csrf_token }}");
            xhr.setRequestHeader("PromptID", String(promptID).split("_")[0]);
            xhr.setRequestHeader("length", recordingTime);

            progressBar = document.getElementById('progress');
            progressBar.value = 0;
            $('#progress').show();

            // Visualize upload progress
            xhr.upload.onprogress = function (e) {
                if (e.lengthComputable) {
                    progressBar.value = (e.loaded / e.total) * 100;
                    progressBar.textContent = progressBar.value; // Fallback for unsupported browsers.
                }
            };

            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                        writeMessages($.parseJSON(xhr.response));
                } else if (xhr.readyState == 4 && xhr.status == 400 || xhr.readyState == 4 && xhr.status == 500) {
                    alert("Error while Uploading - The admins have been notified. Please try again later")
                }
            };
            xhr.send(formData);
        }

This time, I left some more of our individual code in this function. You can ignore everything after the CSRF Token, except for the last line which actually triggers the upload. So what is going on here?

In the stopRecorder Callback you will get a blob, which you can pass to your upload function. Because video files are much larger, we need to create a FormData object. Any additional information, like custom variables, IDs and so on, can be set in the header. Remember to set the CSRF Token (3 years ago I used another function for this instead of just letting Django set it, this depends if your Javascript is part of the Template or not, just get the token somehow, from the cookie, the hidden form or directly in the template).
We can also show a progress bar of the upload (if you are using bootstrap) and when the upload is finished we can have the Django View return some information and display it on the page for example.

If you want to configure the recorder, please have a look at the github Page, but it seems that as of 2018, some options do not work. Here is my current configuration:

                        recorder = RecordRTC(camera, {
                            recorderType: MediaStreamRecorder,
                            type: 'video',
                            mimeType: 'video/webm',  // defaults to vp8 in Firefox 59
                            disableLogs: false,
                            //video: {width: 320, height: 240},  // seems to have no effect?
                            //mimeType: 'video/webm\;codecs=vp9', // possible codecs=h264, vp8, vp9, in FF 59 and Chrome using OSX only vp8 available?
                            //audioBitsPerSecond: 2000,  // min: 100bps max: 6000bps
                            //videoBitsPerSecond: 6000,  // min: -5000bps max: 100000bps, lower values reduce filesize
                            // using default values equals roughly 20 Mbyte per Minute, vbpr 2000 is less than 1 MB per Minute (but with poor quality)
                            //bitsPerSecond: 128000 // if this line is provided, skip above two
                        });

For some reason, the video size seems to be dependent on the CSS property I use. Also the different codecs doesn’t seem to work or have any effect on OSX. The bitrate controls the filesize and the quality. The result will be a webm file. You can also set it to only record or only record audio. Again – check the official documentation of RecordRTC.

Now let us have a look at the view that you should create in Django:

def upload(request):
    if request.method == 'POST':
        logger.info("Received Upload POST request: %s" % request.META)
        baseDir = settings.BASE_DIR

        try:
            recordingLength = float(request.META['HTTP_LENGTH'])

            if request.user.is_anonymous():
                path = baseDir + "/media/recordings/AnonymousUser/"
                userID = None
            else:
                path = baseDir + "/media/recordings/" + request.user.username + "/"
                userID = request.user.id
            filename = str(uuid.uuid1()) + "-" + datetime.now().strftime("%Y-%m-%d_%H-%M-%S")

            logger.info("Filepath: %s" % path)
            if not os.path.exists(path):
                logger.info("Creating new Folder: %s" % path)
                os.makedirs(path)
            logger.info("Writing file: %s" % filename)
            extension = ".ogg" if promptGroup.type == "audio" else ".webm"
            with open(path + filename + extension, 'wb+') as destination:
                for chunk in request.FILES['blob'].chunks():
                    destination.write(chunk)

            # AudioSegment.from_file(path+filename+".ogg").export(path+filename+".mp3", format="mp3").close()
            # os.remove(path+filename+".ogg")

            Recording.objects.create(path=path, name=filename + extension, created=timezone.now(),
                                                 user_id=userID,
                                                 length=recordingLength)
        except:
            logger.critical("Error while trying to upload! Please check previous Log Messages")
            raise SuspiciousFileOperation
        else:
            # No errors during processing of the uploaded recording - proceeding with updating stats for user
        ....... 
            response = {'total_recordings': stats.total_recordings,

        return JsonResponse(response, safe=False)
    else:
        return redirect('/recorder')

Again, I left more of our individual code here than you need. Key points to take away from here: See how I can access the values I set in the header of the request? Using request.META[key] you can get the values / variables you set. The file itself has to be transferred using chunks due to size. If you want, you could use pydub and ffmpeg to automatically convert the result, I have not enabled this at the moment. In the Response I send JSON but this is totally up to you.

Remember, always be careful when you allow the user to upload custom content. I hope this code is helpful for someone. I you have any more questions, leave a comment below.

Leave a Reply

Your email address will not be published. Required fields are marked *