FastAPI视频流疑问:为何首次全量加载?如何实现首5MB加载?
问题描述
使用FastAPI接口播放视频时,已使用StreamingResponse,但首次加载时浏览器会一次性拉取完整视频(大小和S3存储的一致)。希望实现首次仅加载前5MB(首帧相关内容),曾尝试给S3的get_object设置Range参数但未生效,原代码如下:
from private.boto3_client import boto3_client from fastapi import HTTPException, Response from fastapi.responses import StreamingResponse import io def get_video(video_token, range): response = boto3_client.get_object(Bucket='******', Key=f'{video_token}.mp4') def generate(): for chunk in response['Body'].iter_chunks(): yield chunk return StreamingResponse(content=generate(), media_type='video/mp4')
解决方案
要实现按需加载,核心是正确处理HTTP Range请求头,同时给浏览器返回符合规范的响应头,让浏览器识别并支持分段加载。具体实现如下:
- 解析浏览器发送的
Range请求头:存在则按请求范围拉取;不存在则主动返回前5MB内容,同时告知浏览器支持分段加载。 - 给S3的
get_object传入格式正确的Range参数,格式为bytes=start-end。 - 构造响应时,必须设置
Accept-Ranges、Content-Range、Content-Length等关键头,并返回206 Partial Content状态码。
修改后的代码示例:
from private.boto3_client import boto3_client from fastapi import HTTPException, Header from fastapi.responses import StreamingResponse def get_video(video_token, range: str | None = Header(None)): bucket_name = '******' key = f'{video_token}.mp4' # 获取视频总大小 try: head_response = boto3_client.head_object(Bucket=bucket_name, Key=key) total_size = head_response['ContentLength'] except Exception as e: raise HTTPException(status_code=404, detail="Video not found") # 处理Range请求,无请求时默认返回前5MB if range: # 解析Range头,格式如"bytes=0-"或"bytes=1024-2048" start_str = range.strip().split('=')[1] if '-' in start_str: start, end = start_str.split('-') start = int(start) if start else 0 end = int(end) if end else total_size - 1 else: start = int(start_str) end = total_size - 1 else: # 首次请求返回前5MB,字节范围为0到5242879(5*1024*1024-1) start = 0 end = min(5 * 1024 * 1024 - 1, total_size - 1) # 向S3请求指定范围的内容 try: s3_response = boto3_client.get_object( Bucket=bucket_name, Key=key, Range=f'bytes={start}-{end}' ) except Exception as e: raise HTTPException(status_code=416, detail="Requested range not satisfiable") # 计算当前片段的内容长度 content_length = end - start + 1 # 构造分段响应头 headers = { 'Accept-Ranges': 'bytes', 'Content-Range': f'bytes {start}-{end}/{total_size}', 'Content-Length': str(content_length) } def generate(): # 按1MB分块返回,避免一次性加载 for chunk in s3_response['Body'].iter_chunks(chunk_size=1024*1024): yield chunk # 返回206分段响应 return StreamingResponse( content=generate(), media_type='video/mp4', status_code=206, headers=headers )
关键说明
- Range参数格式:S3的
Range参数必须严格遵循bytes=start-end格式,之前未生效大概率是参数格式错误,或未返回正确响应头导致浏览器不识别分段。 - 响应状态码:必须返回
206 Partial Content,而非默认的200,否则浏览器会判定为完整响应,仍会一次性加载。 - 响应头作用:
Accept-Ranges告知浏览器支持分段加载;Content-Range标明当前返回片段的范围和视频总大小;Content-Length是当前片段的字节数,这三个头是实现分段加载的核心。
内容的提问来源于stack exchange,提问作者a55le
相关产品推荐
相关产品推荐

