因为后端返回的是视频的文件流,并不是视频文件地址。后端返回流的时候要为header设置一些参数,否则部分浏览器会出现问题(如Google Chrome...)。

django中常用返回文件流写法:

class VideoAPIView(APIView):
    """
    视频视图  

    - 参数位置:
            查询参数

        - 请求参数:
            - 路径参数:
                - `id`: 文件id 必传

        - 成功返回参数:
            ```
            文件下载到本地
            ```

        - 失败返回参数:
            ```
            {
                "rtCode": 0,
                "rtMsg": "异常信息",
                "rtData": ""
            }
            ```
    """
    def get(self, request, file_info_id):
        try:
            contents_post = {
                "query": {
                    "match": {
                        "_id": file_info_id
                    }
                },
                "script": {
                    "source": "ctx._source['visits_nums'] = ctx._source['visits_nums']+1"
                }
            }
            elasticsearch.update_by_query(index=constants.ELASTICSEARCH_INDEX, body=contents_post)
            fileinfo_obj = models.FileInfo.objects.filter(file_info_id=file_info_id, delete_flag=FileInfo.DeleteFlagEnum.NOT_DELETE.value, file_type=FileInfo.FileTypeEnum.VIDEO.value)
            video_name = fileinfo_obj.first().file_url
        except models.FileInfo.DoesNotExist:
            raise exceptions.ValidationError("数据不存在")
        photo_path = constants.VIDEO_PATH
        file_url_path = os.path.join(photo_path, video_name)
        file_size = os.path.getsize(file_url_path)
        response = FileResponse(open(file_url_path, 'rb'))
        response['Content-Length'] = str(file_size)
        response['Content-Type'] = 'application/octet-stream'
        response['Content-Disposition'] = "attachment; filename={}".format(urlquote(video_name))
        return response

这么写对于返回视频文件流,前端使用video标签展示时,就会出现部分浏览器无法拖动进度条快进问题。

解决:在返回的header中添加Content-Range和Accept-Ranges参数

class VideoAPIView(APIView):
    """
    视频视图

    - 参数位置:
            查询参数

        - 请求参数:
            - 路径参数:
                - `id`: 文件id 必传

        - 成功返回参数:
            ```
            文件下载到本地
            ```

        - 失败返回参数:
            ```
            {
                "rtCode": 0,
                "rtMsg": "异常信息",
                "rtData": ""
            }
            ```
    """
    def get(self, request, file_info_id):
        try:
            contents_post = {
                "query": {
                    "match": {
                        "_id": file_info_id
                    }
                },
                "script": {
                    "source": "ctx._source['visits_nums'] = ctx._source['visits_nums']+1"
                }
            }
            elasticsearch.update_by_query(index=constants.ELASTICSEARCH_INDEX, body=contents_post)
            fileinfo_obj = models.FileInfo.objects.filter(file_info_id=file_info_id, delete_flag=FileInfo.DeleteFlagEnum.NOT_DELETE.value, file_type=FileInfo.FileTypeEnum.VIDEO.value)
            video_name = fileinfo_obj.first().file_url
        except models.FileInfo.DoesNotExist:
            raise exceptions.ValidationError("数据不存在")
        photo_path = constants.VIDEO_PATH
        file_url_path = os.path.join(photo_path, video_name)
        file_size = os.path.getsize(file_url_path)
        response = FileResponse(open(file_url_path, 'rb'))
        response['Content-Length'] = str(file_size)
        response['Content-Type'] = 'application/octet-stream'
        response['Content-Range'] = f'bytes 0-{str(file_size)}/{str(file_size)}'
        response['Accept-Ranges'] = 'bytes'
        response['Content-Disposition'] = "attachment; filename={}".format(urlquote(video_name))
        return response

关于Content-Range参数

用于响应头中,在发出带 Range 的请求后,服务器会在 Content-Range 头部返回当前接受的范围和文件总大小。一般格式:

Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth]

例如:

Content-Range: bytes 0-499/22400

0-499 是指当前发送的数据的范围,而 22400 则是文件的总大小。

而在响应完成后,返回的响应头内容也不同:

HTTP/1.1 200 Ok(不使用断点续传方式) 
HTTP/1.1 206 Partial Content(使用断点续传方式)

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐