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

Chrome浏览器Video标签拖动进度条请求错误字节范围,FASTAPI服务异常

解决FastAPI视频流在Chrome中拖动进度条卡顿/重置的问题

问题根源

你的代码存在两个关键问题,导致Chrome无法正确处理视频分段请求:

  1. Content-Range格式不符合HTTP规范:HTTP规定Content-Range的区间是闭区间(start-end包含两端字节),原代码中end取值为start + CHUNK_SIZE,实际应该是start + CHUNK_SIZE - 1,否则返回的字节范围会超出文件实际索引。
  2. 未正确处理Chrome的range请求格式:Chrome拖动进度条时会发送bytes=xxx-(仅指定起始位置,无结束位置)的请求,原代码直接分割后将空字符串转为整数会引发错误,导致响应无效,Chrome停止后续请求。

修复后的FastAPI后端代码

以下是修正后的核心代码,处理了所有合法的range请求,并严格遵循HTTP规范:

from fastapi import APIRouter, Header, Response, HTTPException
from pathlib import Path
import aiofiles

video_router = APIRouter()

@video_router.get("/video/file/{folder}/{name}")
async def video_endpoint(folder: str, name: str, range: str = Header(None)):
    CHUNK_SIZE = 2 * 1024 * 1024  # 2MB 分段大小,可根据需求调整
    video_path = Path(f"./media/{folder}/{name}")
    
    # 检查文件是否存在
    if not video_path.exists():
        raise HTTPException(status_code=404, detail="Video not found")
    
    video_size = video_path.stat().st_size

    # 处理无range头的请求(首次加载或直接请求完整视频)
    if not range:
        async with aiofiles.open(video_path, "rb") as f:
            data = await f.read()
        return Response(data, media_type="video/mp4")

    # 解析range请求
    try:
        range_part = range.replace("bytes=", "")
        start_str, end_str = range_part.split("-", 1)
        start = int(start_str) if start_str.strip() else 0
        
        # 处理Chrome发送的仅含起始位置的range请求
        if not end_str.strip():
            end = video_size - 1  # 文件最后一个字节的索引
        else:
            end = int(end_str)
            end = min(end, video_size - 1)  # 确保不超出文件范围
        
        start = max(start, 0)  # 确保起始位置不小于0
    except ValueError:
        # range格式无效,返回416状态码
        raise HTTPException(status_code=416, detail="Invalid range request")

    # 调整分段结束位置,避免超过设定的分段大小
    end = min(end, start + CHUNK_SIZE - 1)

    # 异步读取文件分段(避免阻塞事件循环)
    async with aiofiles.open(video_path, "rb") as f:
        await f.seek(start)
        data = await f.read(end - start + 1)

    # 构造符合规范的响应头
    headers = {
        'Content-Range': f'bytes {start}-{end}/{video_size}',
        'Accept-Ranges': 'bytes',
        'Content-Length': str(end - start + 1)  # 明确返回的字节数
    }

    return Response(data, status_code=206, headers=headers, media_type="video/mp4")

额外优化:使用StreamingResponse流式返回(可选)

如果视频文件较大,使用流式返回可以减少内存占用,提升性能:

@video_router.get("/video/file/{folder}/{name}")
async def video_stream(folder: str, name: str, range: str = Header(None)):
    CHUNK_SIZE = 2 * 1024 * 1024
    video_path = Path(f"./media/{folder}/{name}")
    
    if not video_path.exists():
        raise HTTPException(status_code=404, detail="Video not found")
    
    video_size = video_path.stat().st_size
    start = 0
    end = video_size - 1

    if range:
        try:
            range_part = range.replace("bytes=", "")
            start_str, end_str = range_part.split("-", 1)
            start = int(start_str) if start_str.strip() else 0
            if end_str.strip():
                end = int(end_str)
                end = min(end, video_size - 1)
            start = max(start, 0)
        except ValueError:
            raise HTTPException(status_code=416, detail="Invalid range request")

    # 异步生成器,流式返回数据
    async def stream_generator():
        async with aiofiles.open(video_path, "rb") as f:
            await f.seek(start)
            remaining_bytes = end - start + 1
            while remaining_bytes > 0:
                chunk = await f.read(min(CHUNK_SIZE, remaining_bytes))
                if not chunk:
                    break
                yield chunk
                remaining_bytes -= len(chunk)

    headers = {
        'Content-Range': f'bytes {start}-{end}/{video_size}',
        'Accept-Ranges': 'bytes',
        'Content-Length': str(end - start + 1)
    }

    return StreamingResponse(stream_generator(), status_code=206, headers=headers, media_type="video/mp4")

前端注意事项

  1. 跨域配置:如果前端和FastAPI服务不在同一域名下,必须在FastAPI中添加CORS中间件:
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["你的前端域名"],  # 生产环境请勿使用"*"
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
  1. 原React的video标签无需修改,保持现有配置即可。

替代方案

如果上述修正后仍有问题,可以尝试以下方案:

  • 使用ffmpeg将视频转成HLS(HTTP Live Streaming)格式,FastAPI返回M3U8索引文件和TS分片,前端使用video.js或hls.js播放,这种方式对Chrome的兼容性更好,支持更灵活的进度控制。
  • 直接使用FastAPI静态文件服务:将视频文件放在静态目录下,通过StaticFiles挂载,FastAPI会自动处理206分段请求,无需手动实现:
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

app = FastAPI()
app.mount("/video", StaticFiles(directory="media"), name="video")

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 18:24:54