I'm using Python2.7, django==1.7
and uwsgi
for streaming video/mp4 file to iPhone
player.
My code is as below:
def stream(request):
with open('/path/video.mp4', 'r') as video_file:
response = HttpResponse(video_file.read(), content_type='video/mp4')
response['Content-Disposition'] = 'inline; filename=%s' % 'video.mp4'
return response
video_file.close
When i use some small video (less than 1MB), it streams in browser, but in iPhone palyer i have this error:
[uwsgi-http key: 127.0.0.1:8008 client_addr: 192.168.0.172 client_port: 14563] hr_write(): Broken pipe [plugins/http/http.c line 564]
And when the video size is more that 5MB, it doesn't stream in both (means browser and iPhone player) with same error.
I tried to do that by chunk chunk returning using StreamHttpRespose as below:
def read(chunksize=8192):
with open('/path/video.mp4', 'rb') as video_file:
byte = video_file.read(chunksize)
while byte:
yield byte
return StreamingHttpResponse(read(), content_type='video/mp4')
But there is the same error: Broken pipe
.
fyi I can stream pdf and image files. This problem is only with mp4 files. And also i changed the content_type to 'video-mpeg', the browser downloaded that, while i want to prevent file downloading.
What's your idea? Any solution!!?
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
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 ;)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With