跳到主要内容

Video 跨域播放、预加载缓存与防盗链:一条链路怎么讲清楚?

0. 面试速答(30 秒版 TL;DR)

  • 视频跨域播放不是只配一个 Access-Control-Allow-Origin 就结束了,真正要一起看的是:媒体资源 CORS、Range、CDN 缓存、播放器预加载策略、鉴权与防盗链
  • <video> 能不能播,和“能不能被 JS 正常读取状态/画到 canvas/做字幕和封面处理”不是同一件事;这时 crossorigin、响应头、Cookie 策略都会影响结果。
  • 预加载不是越 aggressive 越好。preload="auto"metadata、首片预取、播放器预热,都要按场景选,不然很容易把流量和缓存打爆。
  • 防盗链不要只靠 Referer。更稳的做法是:带过期时间的签名 URL / 鉴权 token + CDN 校验 + HTTPSReferer 只能当辅助手段。

1. 先看全链路:播放失败往往不是单点故障

视频页面里常见的几个症状:

  • 页面能看到播放器,但一直转圈
  • 某些浏览器能播,某些浏览器报跨域
  • 拖动时重复回源,缓存命中很差
  • 外站把你的资源地址一贴,照样能盗播

这些问题往往连在一起。

回答这类问题时,建议固定成 5 步:

  1. 资源是整文件还是分片流。
  2. 浏览器是不是跨域访问。
  3. 服务端是否正确支持 Range、缓存头、CORS。
  4. 前端预加载策略是不是过度。
  5. 链接是不是具备签名和过期机制。

2. 跨域播放到底在解决什么

2.1 先分清两个层次

第一层是“视频能不能播放”。

第二层是“前端脚本能不能安全地进一步操作视频资源”。

很多时候:

  • 资源地址即使跨域,浏览器也可能直接播放。
  • 但如果你要做这些事,就更依赖规范的跨域配置:
    • 把视频帧画到 canvas
    • 读取更完整的媒体状态
    • 做截图、封面抽帧、像素处理
    • 某些播放器 SDK 的高级能力

2.2 前端常见写法

<video
controls
preload="metadata"
crossorigin="anonymous"
src="https://cdn.example.com/video/course-01.mp4"
></video>

这里的关键点:

  • crossorigin="anonymous" 表示跨域请求不带凭证。
  • 如果服务端允许匿名跨域,就要返回匹配的 CORS 响应头。
  • 如果你走的是 Cookie 鉴权,就不能再简单用匿名模式,要重新设计鉴权方式。

2.3 服务端最少要配什么

对于跨域媒体资源,至少要确认:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Expose-Headers,必要时暴露 Content-LengthContent-Range
  • Accept-Ranges: bytes
  • 正确的 Content-Type

一个常见的 Nginx 例子:

location /video/ {
add_header Access-Control-Allow-Origin https://www.example.com always;
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
add_header Access-Control-Expose-Headers "Content-Length, Content-Range" always;
add_header Accept-Ranges bytes always;
}

如果是 HLS,还要继续检查:

  • m3u8 清单是否允许跨域
  • 分片文件 ts / m4s 是否同样允许跨域

常见坑是:清单放行了,分片没放行,最后表现成“播放器偶尔能播、偶尔报错”。

3. 为什么视频播放很依赖 Range

Range 的作用不是锦上添花,而是很多视频体验的基础能力。

没有 Range,会直接影响:

  • 长视频拖动
  • 断点续播
  • 浏览器按需拉取
  • CDN 对部分内容的缓存策略

正常情况下,播放器或浏览器会发这种请求:

Range: bytes=1048576-

服务端应该返回:

  • 206 Partial Content
  • Content-Range
  • Accept-Ranges: bytes

如果这个链路没打通,典型现象就是:

  • 拖动以后重新从头下
  • 首帧慢
  • 大文件回源压力很高

4. 预加载怎么选,不要只会写 preload="auto"

4.1 preload 的真实含义

preload 是浏览器的提示,不是强制命令。

常见取值:

  • none:尽量不预拉
  • metadata:只拿元信息
  • auto:允许浏览器积极加载

但实际加载多少,还受这些因素影响:

  • 浏览器策略
  • 网络环境
  • 是否自动播放
  • 是否在可视区
  • 是否是移动端省流模式

4.2 场景化选择

场景更常见的选择原因
长视频详情页metadata先拿时长、封面、基础信息,避免一进页就拉大量流量
短视频信息流下一条预热首片预取或业务层预热比单纯 auto 更可控
直播间进入房间后立即拉流延迟优先,通常不是靠原生 preload 控制
后台管理播放nonemetadata使用频率低,节省带宽

4.3 一个更务实的预加载原则

  • 不要让页面首屏同时对多个视频都 auto
  • 不要把“预加载”理解成“把整个视频缓存到本地”。
  • 预加载通常只解决首帧,不解决所有卡顿问题。

如果是信息流,常用思路反而是:

  • 当前视频正常播放
  • 只对下一条做轻量预热
  • 离开可视区立即暂停并释放资源

5. 缓存到底缓存在什么地方

视频缓存至少有 3 层:

  1. 浏览器缓存:受 Cache-Control、资源类型、浏览器策略影响。
  2. CDN 缓存:核心看 URL、查询参数、分片粒度、回源规则。
  3. 播放器缓冲区:这是运行时 buffer,不等于持久缓存。

很多人把“buffer 很长”误以为“缓存已经很好”。这是错的。

5.1 前端更该关心的缓存头

  • Cache-Control
  • ETag
  • Last-Modified
  • Vary

但视频链路里,最关键的往往不是协商缓存本身,而是:

  • 分片 URL 是否稳定
  • 鉴权参数是否导致缓存穿透
  • CDN 是否把签名参数纳入缓存键

5.2 一个典型误区

如果你给每个视频请求都挂上强随机参数,例如:

https://cdn.example.com/video/lesson.mp4?t=1712749200&nonce=abc123

那 CDN 很可能完全失去缓存价值,因为每个 URL 都变了。

更合理的做法通常是:

  • 使用短期有效、可复用的签名 URL
  • 控制签名参数结构,避免无意义抖动
  • 对 HLS 清单和分片分别制定缓存策略

6. 防盗链怎么做才不脆弱

6.1 只靠 Referer 为什么不够

Referer 防盗链的优点是简单,但问题也明显:

  • 某些浏览器或隐私策略会裁剪 Referer
  • App、内嵌 WebView、代理环境可能不稳定
  • 被拿到真实地址后,单纯 Referer 很容易被绕过

所以它更适合做:

  • 粗粒度拦截
  • 异常流量辅助识别

不适合做唯一鉴权手段。

6.2 更稳的方案

更推荐的组合是:

  1. URL 带签名
  2. 签名带过期时间
  3. 服务端或 CDN 校验签名
  4. 结合 HTTPS,防止链路中间人轻易截获

例如:

https://cdn.example.com/video/course-01.m3u8?expires=1712749800&sign=abcdef123456

校验要点通常包括:

  • 路径
  • 过期时间
  • 用户或设备维度
  • 防重放字段

6.3 HLS 场景下的防盗链要更细

HLS 不止一个 URL:

  • 一个 m3u8
  • 多个 tsm4s
  • 可能还有密钥文件

所以防盗链不能只保护主播放地址,还要覆盖子资源,不然就会出现:

  • 清单需要登录
  • 分片却能被直接扫走

7. 一套常见的落地配置思路

7.1 点播 MP4

  • 资源放 CDN
  • 支持 Range
  • Cache-Control 允许 CDN 缓存
  • 使用带过期时间的签名 URL
  • 需要前端抽帧时再加 crossorigin

7.2 HLS 点播

  • m3u8 与分片都配 CORS
  • 清单可短缓存,分片可长缓存
  • 签名要覆盖清单和分片
  • 非 Safari 走 hls.js 时要联调分片跨域

7.3 短视频信息流

  • 当前视频正常播放
  • 下一条轻量预热
  • 离屏后暂停并释放
  • 避免多个视频同时 aggressive preload

8. 常见追问

8.1 为什么视频地址浏览器里能直接打开,但页面里播放器报跨域?

因为“浏览器地址栏直接访问资源”和“页面脚本上下文里跨域访问媒体资源”不是同一个安全模型。尤其当你要结合 crossorigincanvas、播放器库时,跨域规则会更严格。

8.2 为什么明明配了 CORS,拖动还是卡?

因为拖动更多依赖 Range、关键帧布局、分片长度、CDN 命中,不是只依赖 CORS。

8.3 为什么做了签名 URL,缓存反而变差?

常见原因是签名参数设计不稳定,导致同一个资源不断生成不同 URL,CDN 无法复用缓存。

9. 易错点

  • 把“能播放”和“能跨域安全处理媒体”混为一谈。
  • 只给主文件配 CORS,忘了 HLS 分片和密钥文件。
  • 只配 CORS,不配 Range
  • preload="auto" 当成统一最优解。
  • 只靠 Referer 做防盗链。

10. 速记版

  • 跨域播放要一起看:CORS + crossorigin + Range + 子资源
  • 预加载要按场景选,重点是首帧收益和流量成本平衡
  • 缓存要区分:浏览器缓存、CDN 缓存、播放器 buffer
  • 防盗链优先:签名 URL / token 鉴权Referer 只做辅助。