[Fixed]-Python – Django: Streaming video/mp4 file using HttpResponse

35πŸ‘

βœ…

I had the same problem and did a lot of digging before finding a workable solution!

Apparently the Accept Ranges header is needed for HTML5 video controls to work (https://stackoverflow.com/a/24977085/4264463). So, we need to both parse the requested range from HTTP_RANGE and return Content-Range with the response. The generator that is passed to StreamingHttpResponse also needs to return content based on this range as well (by offset and length). I’ve found the follow snippet that works great (from http://codegist.net/snippet/python/range_streamingpy_dcwatson_python):

import os
import re
import mimetypes
from wsgiref.util import FileWrapper

from django.http.response import StreamingHttpResponse


range_re = re.compile(r'bytes\s*=\s*(\d+)\s*-\s*(\d*)', re.I)


class RangeFileWrapper(object):
    def __init__(self, filelike, blksize=8192, offset=0, length=None):
        self.filelike = filelike
        self.filelike.seek(offset, os.SEEK_SET)
        self.remaining = length
        self.blksize = blksize

    def close(self):
        if hasattr(self.filelike, 'close'):
            self.filelike.close()

    def __iter__(self):
        return self

    def __next__(self):
        if self.remaining is None:
            # If remaining is None, we're reading the entire file.
            data = self.filelike.read(self.blksize)
            if data:
                return data
            raise StopIteration()
        else:
            if self.remaining <= 0:
                raise StopIteration()
            data = self.filelike.read(min(self.remaining, self.blksize))
            if not data:
                raise StopIteration()
            self.remaining -= len(data)
            return data


def stream_video(request, path):
    range_header = request.META.get('HTTP_RANGE', '').strip()
    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 = int(last_byte) if last_byte else size - 1
        if last_byte >= size:
            last_byte = size - 1
        length = last_byte - first_byte + 1
        resp = StreamingHttpResponse(RangeFileWrapper(open(path, 'rb'), 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:
        resp = StreamingHttpResponse(FileWrapper(open(path, 'rb')), content_type=content_type)
        resp['Content-Length'] = str(size)
    resp['Accept-Ranges'] = 'bytes'
    return resp
πŸ‘€Kevin Lee

2πŸ‘

After a lot of search, i didn’t find my solution.

So, i tried to create a stream-server easily using nodejs from html5-video-streamer.js reference as below:

var http       = require('http'),
    fs         = require('fs'),
    url        = require('url'),
    basePath   = '/var/www/my_project/media/',
    baseUrl    = 'Your Domain or IP',
    basePort   = 8081;

http.createServer(function (req, res) {

    // Get params from request.
    var params    = url.parse(req.url, true).query, 
        filePath  = basePath + params.type + '/' + params.name,
        stat      = fs.statSync(filePath),
        total     = stat.size;

      if (req.headers['range']) {
        var range         = req.headers.range,
            parts         = range.replace(/bytes=/, "").split("-"),
            partialstart  = parts[0],
            partialend    = parts[1],
            start         = parseInt(partialstart, 10),
            end           = partialend ? parseInt(partialend, 10) : total-1,
            chunksize     = (end-start)+1;

        var file = fs.createReadStream(filePath, {start: start, end: end});
        res.writeHead(206, { 'Content-Range'  : 'bytes ' + start + '-' + end + '/' + total,
                             'Accept-Ranges'  : 'bytes',
                             'Content-Length' : chunksize,
                             'Content-Type'   : 'video/mp4' });
        file.pipe(res);

        // Close file at end of stream.
        file.on('end', function(){
          file.close();
        });
      } 
      else {
        res.writeHead(206, { 'Content-Length'   : total,
                             'Content-Type'     : 'video/mp4' });

        var file = fs.createReadStream(filePath);
        file.pipe(res);

        // Close file at end of stream.
        file.on('end', function(){
          file.close();
        });
      }
 }).listen(basePort, baseUrl);

Now i have separate stream-server with nodejs that streams mp4 files beside python project that provides my APIs.

I’m aware It’s not my solution, but it works for me πŸ˜‰

πŸ‘€Aida.Mirabadi

Leave a comment