技术知识文章集合TECHNICAL ARCHIVE · 807 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTBE

Nginx 伺服 mp4 给 Safari 的正确方式

所属分类
Backend
文件格式
Markdown
原始路径
Backend/Nginx 伺服 mp4 给 Safari 的正确方式
本文目录4 个章节
  1. 问题背景
  2. 采纳回答(7 赞):视频根本不是 nginx 在伺服
  3. 另一个坑(1 赞):sub_filter_types * 会杀死流式播放
  4. 整理者注

Nginx 伺服 mp4 给 Safari 的正确方式

Category(分类): Backend Status: 已读

原文:How to configure nginx to properly serve mp4 videos to Safari?(StackOverflow 问答,2018)。 问答内容经 StackExchange API 核实整理。

问题背景

在页面中使用 <video> 标签播放视频:

<video autoplay muted loop preload="auto" poster="http://my.ip.add.ress/static/my_video_image.jpg">
    <source src="http://my.ip.add.ress/static/my_video.mp4" type="video/mp4" />
    <source src="http://my.ip.add.ress/static/my_video.webm" type="video/webm" />
</video>

静态文件(css、js、图片)都能正常伺服,视频在 Chrome 和 Firefox 中也能正常播放。

唯独 Safari 不行。抓包对比发现差异:

  • Safari 发出的请求带有 Range: bytes=0-1 头,期望服务器返回 206 Partial Content 分片响应;
  • 但服务器返回的是 200 OK + 整个文件(Content-Length: 10732143),忽略了 Range 头;
  • 在正常工作的视频网站上,同样的 Range: bytes=0-1 请求会得到 206 + Accept-Ranges: bytes 响应。

提问者当时以为是 nginx 忽略了 Range 头,问如何让 nginx "注意"这个头。

当时的 nginx 配置:

server {
    listen 80;
    server_name my.ip.add.ress;

    location = /favicon.ico { access_log off; log_not_found off; }

    location /static/ {
        alias /home/website/my_python_virtual_env/my_project/static_folder_containing_mp4_videos/;
    }

    location / {
        # gunicorn to django
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}

采纳回答(7 赞):视频根本不是 nginx 在伺服

提问者自答(被采纳):

My nginx instance was not setup to serve the media. Anything from /media/ was being served by django. Django does not properly serve mp4 videos for Safari because it doesn't work with Range requests. It will serve them properly enough for Chrome to work though!

根因:视频文件放在 /media/ 路径下,而 nginx 配置里没有对应 location,请求全部落到了反代 location / 后面的 Django(gunicorn)上。Django 开发服务器对 Range 请求的支持不完整——对 Chrome "够用",但 Safari 严格要求 206 分片响应,拿到 200 全量就直接拒绝播放。

修复很简单——给视频目录加一条 location,让 nginx 直接伺服静态文件:

server {
    listen 80;
    server_name my.ip.add.ress;

    location = /favicon.ico { access_log off; log_not_found off; }

    # 静态文件仍然保留
    location /static/ {
        alias /home/website/my_python_virtual_env/my_project/static_folder_containing_static_files/;
    }

    # 关键新增:视频在 media 目录而不是 static 目录,之前一直由 django 伺服
    location /media/ {
        alias /home/website/my_python_virtual_env/my_project/media_folder_containing_mp4_videos/;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}

nginx 作为静态文件服务器天然支持 Range 请求,会自动返回 206 和 Accept-Ranges: bytes。

另一个坑(1 赞):sub_filter_types * 会杀死流式播放

另一位回答者遇到了相似的深坑:

  • 现象:iOS / Safari 上 MP4 播不出来,浏览器只显示一个带斜杠的播放符号;<video autoPlay loop muted playsInline> 属性都配齐了也没用;
  • 排查:Safari 需要 206 分片响应才能流式播放,而他们的 nginx 配置了 sub_filter_types *——这会把本来的静态文件响应变成"内存中修改过的响应",这类响应不支持 Accept-Ranges: bytes 流式传输;
  • 修复:sub_filter 只对真正需要文本替换的类型开启:
http {
    server {
        (...)
-       sub_filter_types *;
+       sub_filter_types text/html application/javascript;
    }
}

整理者注

排查"Safari 视频 200 而非 206"的思路总结:

  1. 先确认响应头:有没有 Accept-Ranges: bytes?有没有 Content-Range?Safari 对 206 的要求比 Chrome/Firefox 严格得多;
  2. 确认是谁在伺服文件:请求是否真的命中了 nginx 的静态 location,还是被反代给了 Django / Node / 其他应用服务器?应用服务器大多不完整支持 Range;
  3. 检查 sub_filter / 破坏性的中间处理:sub_filter_types *、动态 gzip 到内存响应等都会让静态响应失去分片能力;
  4. 最稳妥的方案永远是:视频文件交给 nginx 静态伺服(或专业 CDN/对象存储),别经过应用层。

另可参考本站《Nginx 伺服 mp4 视频流的配置参考》《Nginx 伺服大体积 mp4 文件效率问题排查》两篇。

807 DOCUMENTS · 12 COLLECTIONS
ARCHIVE SEARCH807 篇文章