You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
    )
关键说明
  1. Range参数格式:S3的Range参数必须严格遵循bytes=start-end格式,之前未生效大概率是参数格式错误,或未返回正确响应头导致浏览器不识别分段。
  2. 响应状态码:必须返回206 Partial Content,而非默认的200,否则浏览器会判定为完整响应,仍会一次性加载。
  3. 响应头作用:Accept-Ranges告知浏览器支持分段加载;Content-Range标明当前返回片段的范围和视频总大小;Content-Length是当前片段的字节数,这三个头是实现分段加载的核心。

内容的提问来源于stack exchange,提问作者a55le

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 03:35:11