ThinkPHP 6.0 实现断点续传视频流需正确响应 HTTP Range 请求:解析 Range 头、返回 206 状态码及 Accept-Ranges、Content-Range、Content-Length 等规范响应头,用 fopen/fseek/fread 分块读取大文件,避免内存溢出,并规避 Nginx、CDN 和输出缓冲等部署陷阱。

ThinkPHP 6.0 实现支持断点续传的视频流服务,核心在于正确响应 HTTP Range 请求——不是简单输出文件,而是按需读取字节段、返回 206 状态码及规范响应头。只要后端能识别 Range 头、精准定位、分块读取、不加载全文件到内存,前端 video 标签就能自由拖动、跳转、恢复播放。
识别并解析 Range 请求头
客户端(如浏览器 video 标签)拖动进度条时,会自动带上 Range 请求头,例如 Range: bytes=102400-204799。TP6 中需主动检查该字段:
- 使用 $request->header('range') 获取原始值,不能依赖 $_SERVER['HTTP_RANGE'](部分 SAPI 下可能为空)
- 用正则提取起始和结束位置:bytes=(\d+)-(\d*),注意处理末尾无数字的情况(如 bytes=102400- 表示“从该位置到文件末尾”)
- 若未检测到 Range 头,应走普通 200 响应流程;否则必须进入断点逻辑,避免混用状态码
构造合法的 206 Partial Content 响应
仅返回部分内容还不够,HTTP 协议对 206 响应有严格要求,缺一不可:
- 状态码必须是 206,不能是 200 或其他
- 响应头必须包含:Accept-Ranges: bytes(告诉客户端支持范围请求)、Content-Range: bytes start-end/total_size(格式要准确,start 和 end 都是含边界,total_size 是文件总字节数)
- Content-Length 必须等于当前片段字节数(end - start + 1),不能填整个文件大小
- Content-Type 设为 video/mp4、video/webm 等对应 MIME 类型,避免浏览器解析失败
安全高效读取大文件片段
视频文件往往几百 MB 甚至 GB 级,绝不能用 file_get_contents() 加载全文——极易内存溢出。推荐做法是:
- 用 fopen($filePath, 'rb') 打开只读二进制流
- 调用 fseek($fp, $start) 定位到起始字节(注意:fseek 对大文件稳定,比 array_slice 等内存操作更可靠)
- 循环 fread($fp, $bufferSize)(建议 8KB–64KB),每次读一块写入响应,同时检查是否已读满所需长度
- 读完立即 fclose($fp),释放资源
规避常见部署陷阱
本地测试正常,上线后断点失效?多数问题出在中间层干扰:
- Nginx 默认会剥离 Range 头,需确认配置中开启 underscores_in_headers on; 并显式透传:proxy_pass_request_headers on;
- CDN 或反向代理可能缓存了 200 响应,导致后续 Range 请求被错误返回全量内容,建议对视频路径禁用 CDN 缓存或设置 Cache-Control: no-cache
- TP6 的 output 缓冲(ob_start)若未关闭,会导致响应头延迟发送,引发 206 解析失败,可在响应前加 ob_end_clean()


















