Python - Django:使用HttpResponse流式传输视频/ mp4文件

Aid*_*adi 7 django video video-streaming uwsgi python-2.7

我正在使用Python2.7,django==1.7并将uwsgi视频/ mp4文件流式传输到iPhone播放器.

我的代码如下:

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
Run Code Online (Sandbox Code Playgroud)

当我使用一些小视频(小于1MB),它在浏览器中流,但在iPhone palyer我有这个错误:

[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]

当视频大小超过5MB时,它不会同时传输(意味着浏览器和iPhone播放器)同样的错误.

我尝试使用StreamHttpRespose返回chunk chunk,如下所示:

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')
Run Code Online (Sandbox Code Playgroud)

但是有同样的错误:Broken pipe.

我可以传输pdf和图像文件.此问题仅适用于mp4文件.而且我还将content_type更改为'video-mpeg',浏览器下载了这个,而我想阻止文件下载.

你有什么想法?任何解决方案!!?

Kev*_*Lee 15

我遇到了同样的问题,在找到可行的解决方案之前做了很多挖掘!

显然Accept Ranges,HTML5视频控件可以使用标题(/sf/answers/1748395981/).因此,我们需要解析请求的范围HTTP_RANGE并返回Content-Range响应.传递给它的生成器StreamingHttpResponse也需要根据此范围返回内容(by offsetlength).我发现以下代码片段效果很好(来自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
Run Code Online (Sandbox Code Playgroud)