[Emby]使用nginx反向代理并缓存视频流

Nginx

upstream emby-backend {
     server  127.0.0.1:8096;
     keepalive 10240;
}

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name domain.com;
    index index.html;

    ssl_certificate    /path/to/certificate.pem;
    ssl_certificate_key    /path/to/certificate_key.pem;
    ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    add_header Strict-Transport-Security "max-age=15552000; preload" always;
    error_page 497  https://$host$request_uri;

    location ~ \.well-known {
        allow all;
    }

    client_body_buffer_size 512k;
    client_max_body_size 20M;

    add_header 'Referrer-Policy' 'origin-when-cross-origin';
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    keepalive_timeout 120s;
    keepalive_requests 10240;

    proxy_hide_header X-Powered-By;
    proxy_buffer_size 32k;
    proxy_buffers 4 64k;
    proxy_busy_buffers_size 128k;
    proxy_temp_file_write_size 128k;
    proxy_connect_timeout 1h;
    proxy_send_timeout 1h;
    proxy_read_timeout 1h;

    location = / {
        return 302 web/index.html;
    }

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Protocol $scheme;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header REMOTE-HOST $remote_addr;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Accept-Encoding "";
    proxy_http_version 1.1;

    location / {
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "";
  
        proxy_cache off;
    }

    location /web/ {
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "";

        # 如果你使用cloudflare或其他cdn缓存js、css等文件,可取消下一行的注释来关闭本机缓存
        # proxy_cache off;
        proxy_cache_revalidate on;
        proxy_cache_background_update on;
    }

    location = /embywebsocket {
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "upgrade";
        proxy_cache off;
    }

    location /emby/videos/ {
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "";
        proxy_cache off;
        proxy_buffering off;
    }

    access_log  off;
    error_log  off;
}

缓存分为普通文件缓存和slice分片缓存。
开启slice缓存的作用:
1. 可在播放时提前缓存后几十秒到几分钟的视频文件,所以可实现向后小距离秒调进度。(对于视频源使用rclone挂载的服务器非常有效)
2. 多用户播放热门视频资源只需要请求一起emby或rclone,大大减轻服务器负载和速度。
3. 如果视频需要实时转码,那么同一个转码配置(如同一个码率)只需要转码一次则会被缓存,那么下一次观看不需要实时转码。
4. 只要是缓存过的视频都能实现秒播,且调进度也不会卡顿。
5. 如果你有多条线路反向代理源emby服务器,那么强烈推荐在反向代理服务器上部署视频流缓存。

开启emby的视频slice缓存需要nginx拥有slice插件,可以运行命令

nginx -V

查看输出的

configure arguments

是否有

--with-http_slice_module

这一段参数,如果有则代表可以开启视频流缓存,如果没有需要重新编译nginx并添加此参数即可。
具体配置如下:

# 首先定义缓存位置和缓存大小(定义在http模块中,与upstream和server同级):
# proxy_cache_path 缓存位置(需要提前创建好目录,并赋予nginx的读写权限)
# keys_zone 的值和后面反向代理里面的 proxy_cache 的值要对应一样
# inactive 缓存时间
# max_size 最大缓存大小
# use_temp_path 设置为off则直接将缓存文件写入proxy_cache_path,否则会先写入temp_path,缓存完成后再移动到proxy_cache_path,这样可以保证当缓存出现错误的时候不会出现在缓存文件夹中。注意,如果使用slice缓存,请不要设置use_temp_path为off!!!

proxy_cache_path /var/cache/nginx/emby-videos levels=1:2 keys_zone=emby-videos:100m inactive=15d max_size=500g;
# 然后定义相关变量在http模块中:
map $request_uri $h264Level {
    ~(h264-level=)(.+?)& $2;
}
map $request_uri $h264Profile {
    ~(h264-profile=)(.+?)& $2;
}
# 接下来添加反向代理配置,添加到上面配置好的反向代理配置的最后即可(server模块中)

    location ~ ^/emby/videos/(?<Id>\d+)/stream(?:\.?(?<Container>\w*)) {
        slice 16m;
 
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "";
        proxy_set_header Range $slice_range;
        proxy_ignore_headers Expires Cache-Control Set-Cookie X-Accel-Expires;
        proxy_connect_timeout 15s;

        proxy_cache emby-videos;
        proxy_cache_bypass $no_cache;
        proxy_no_cache $no_cache;
        proxy_cache_valid 200 206 301 302 7d;
        proxy_cache_lock on;
        proxy_cache_lock_age 60s;
        proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
        if ($Container = "") {
            set $container $arg_Container;
        }
        if ($Container != "") {
            set $container $Container;
        }
        if ($container = "") {
            set $no_cache 1;
        }
        set $cache_key "Id=$Id&Container=$container&MediaSourceId=$arg_MediaSourceId";
        if ($arg_Static != "true") {
            set $cache_key "$cache_key&VideoCodec=$arg_VideoCodec&AudioCodec=$arg_AudioCodec&AudioStreamIndex=$arg_AudioStreamIndex&AudioSampleRate=$arg_AudioSampleRate&VideoStreamIndex=$arg_VideoStreamIndex&Width=$arg_Width&Height=$arg_Height&MaxWidth=$arg_MaxWidth&MaxHeight=$arg_MaxHeight&ManifestSubtitles=$arg_ManifestSubtitles&VideoBitrate=$arg_VideoBitrate&AudioBitrate=$arg_AudioBitrate&SubtitleStreamIndex=$arg_SubtitleStreamIndex&SubtitleMethod=$arg_SubtitleMethod&AudioChannels=$arg_AudioChannels&MaxAudioChannels=$arg_MaxAudioChannels&Framerate=$arg_Framerate&MaxFramerate=$arg_MaxFramerate&h264-profile=$h264Profile&h264-level=$h264Level";
            # 如果出现bug可以尝试解除下一行开头的注释
            # set $no_cache 1;
        }
        proxy_cache_key "$cache_key&slicerange=$slice_range";
    }
# 同样在http模块中添加此缓存定义配置(建议提前创建好目录,并赋予nginx的读写权限)

proxy_cache_path /var/cache/nginx/emby levels=1:2 keys_zone=emby:100m max_size=25g inactive=30d use_temp_path=off;

# 接下来添加反向代理配置,添加到上面配置好的反向代理配置的最后即可

    location ~ ^/emby/Items/.*/Images/ {
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "";
        proxy_set_header If-None-Match $http_if_none_match;

        proxy_cache emby;
        proxy_cache_key $request_uri;
        proxy_cache_revalidate on;
        proxy_cache_background_update on;
        proxy_cache_lock on;
    }

# 同样在http模块中添加此缓存定义配置(建议提前创建好目录,并赋予nginx的读写权限)

proxy_cache_path /var/cache/nginx/emby-audios levels=1:2 keys_zone=emby-audios:100m inactive=15d max_size=1g;
    location ~ ^/emby/Audio/(?<Id>\d+)/universal(?:\.?(?<Container>\w*)) {
        slice 8m;
        
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "";
        proxy_set_header Range $slice_range;
        proxy_ignore_headers Expires Cache-Control Set-Cookie X-Accel-Expires;
        proxy_connect_timeout 15s;

        proxy_cache emby-audios;
        proxy_cache_bypass $no_cache;
        proxy_no_cache $no_cache;
        proxy_cache_valid 200 206 301 302 7d;
        proxy_cache_lock on;
        proxy_cache_lock_age 60s;
        proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
        if ($Container = "") {
            set $container $arg_Container;
        }
        if ($Container != "") {
            set $container $Container;
        }
        if ($container = "") {
            set $no_cache 1;
        }
        set $cache_key "Id=$Id&Container=$container&MaxStreamingBitrate=$arg_MaxStreamingBitrate&MaxSampleRate=$arg_MaxSampleRate&TranscodingProtocol=$arg_TranscodingProtocol&TranscodingContainer=$arg_TranscodingContainer&AudioCodec=$arg_AudioCodec&EnableRedirection=$arg_EnableRedirection&EnableRemoteMedia=$arg_EnableRemoteMedia&StartTimeTicks=$arg_StartTimeTicks";
        proxy_cache_key "$cache_key&slicerange=$slice_range";
    }
proxy_cache_path /var/cache/nginx/emby-videos levels=1:2 keys_zone=emby-videos:100m inactive=15d max_size=500g;
map $request_uri $h264Level {
    ~(h264-level=)(.+?)& $2;
}
map $request_uri $h264Profile {
    ~(h264-profile=)(.+?)& $2;
}

proxy_cache_path /var/cache/nginx/emby levels=1:2 keys_zone=emby:100m max_size=25g inactive=30d use_temp_path=off;

proxy_cache_path /var/cache/nginx/emby-audios levels=1:2 keys_zone=emby-audios:100m inactive=15d max_size=1g;

upstream emby-backend {
    server 127.0.0.1:8096;
    keepalive 10240;
}

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name domain.com;
    index index.html;

    ssl_certificate /path/to/certificate.pem;
    ssl_certificate_key /path/to/certificate_key.pem;
    ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    add_header Strict-Transport-Security "max-age=15552000; preload" always;
    error_page 497 https://$host$request_uri;

    location ~ \.well-known {
        allow all;
    }

    client_body_buffer_size 512k;
    client_max_body_size 20M;

    add_header 'Referrer-Policy' 'origin-when-cross-origin';
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    keepalive_timeout 120s;
    keepalive_requests 10240;

    proxy_hide_header X-Powered-By;
    proxy_buffer_size 32k;
    proxy_buffers 4 64k;
    proxy_busy_buffers_size 128k;
    proxy_temp_file_write_size 128k;
    proxy_connect_timeout 1h;
    proxy_send_timeout 1h;
    proxy_read_timeout 1h;

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Protocol $scheme;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header REMOTE-HOST $remote_addr;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Accept-Encoding "";
    proxy_http_version 1.1;

    location = / {
        return 302 web/index.html;
    }

    location / {
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "";

        proxy_cache off;
    }

    location /web/ {
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "";

        # 如果你使用cloudflare或其他cdn缓存js、css等文件,可取消下一行的注释来关闭本机缓存
        # proxy_cache off;
        proxy_cache_revalidate on;
        proxy_cache_background_update on;
    }

    location = /embywebsocket {
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "upgrade";
        proxy_cache off;
    }

    location /emby/videos/ {
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "";
        proxy_cache off;
        proxy_buffering off;
    }

    location ~ ^/emby/videos/(?<Id>\d+)/stream(?:\.?(?<Container>\w*)) {
        slice 16m;
 
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "";
        proxy_set_header Range $slice_range;
        proxy_ignore_headers Expires Cache-Control Set-Cookie X-Accel-Expires;
        proxy_connect_timeout 15s;

        proxy_cache emby-videos;
        proxy_cache_bypass $no_cache;
        proxy_no_cache $no_cache;
        proxy_cache_valid 200 206 301 302 7d;
        proxy_cache_lock on;
        proxy_cache_lock_age 60s;
        proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
        if ($Container = "") {
            set $container $arg_Container;
        }
        if ($Container != "") {
            set $container $Container;
        }
        if ($container = "") {
            set $no_cache 1;
        }
        set $cache_key "Id=$Id&Container=$container&MediaSourceId=$arg_MediaSourceId";
        if ($arg_Static != "true") {
            set $cache_key "$cache_key&VideoCodec=$arg_VideoCodec&AudioCodec=$arg_AudioCodec&AudioStreamIndex=$arg_AudioStreamIndex&AudioSampleRate=$arg_AudioSampleRate&VideoStreamIndex=$arg_VideoStreamIndex&Width=$arg_Width&Height=$arg_Height&MaxWidth=$arg_MaxWidth&MaxHeight=$arg_MaxHeight&ManifestSubtitles=$arg_ManifestSubtitles&VideoBitrate=$arg_VideoBitrate&AudioBitrate=$arg_AudioBitrate&SubtitleStreamIndex=$arg_SubtitleStreamIndex&SubtitleMethod=$arg_SubtitleMethod&AudioChannels=$arg_AudioChannels&MaxAudioChannels=$arg_MaxAudioChannels&Framerate=$arg_Framerate&MaxFramerate=$arg_MaxFramerate&h264-profile=$h264Profile&h264-level=$h264Level";
            # 如果出现bug可以尝试解除下一行开头的注释
            # set $no_cache 1;
        }
        proxy_cache_key "$cache_key&slicerange=$slice_range";
    }

    location ~ ^/emby/Items/.*/Images/ {
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "";
        proxy_set_header If-None-Match $http_if_none_match;
        proxy_set_header Accept-Encoding "";

        proxy_cache emby;
        proxy_cache_key $request_uri;
        proxy_cache_revalidate on;
        proxy_cache_background_update on;
        proxy_cache_lock on;
    }

    location ~ ^/emby/Audio/(?<Id>\d+)/universal(?:\.?(?<Container>\w*)) {
        slice 8m;
        
        proxy_pass http://emby-backend;
        proxy_set_header Host $host;
        proxy_set_header Connection "";
        proxy_set_header Range $slice_range;
        proxy_ignore_headers Expires Cache-Control Set-Cookie X-Accel-Expires;
        proxy_connect_timeout 15s;

        proxy_cache emby-audios;
        proxy_cache_bypass $no_cache;
        proxy_no_cache $no_cache;
        proxy_cache_valid 200 206 301 302 7d;
        proxy_cache_lock on;
        proxy_cache_lock_age 60s;
        proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
        if ($Container = "") {
            set $container $arg_Container;
        }
        if ($Container != "") {
            set $container $Container;
        }
        if ($container = "") {
            set $no_cache 1;
        }
        set $cache_key "Id=$Id&Container=$container&MaxStreamingBitrate=$arg_MaxStreamingBitrate&MaxSampleRate=$arg_MaxSampleRate&TranscodingProtocol=$arg_TranscodingProtocol&TranscodingContainer=$arg_TranscodingContainer&AudioCodec=$arg_AudioCodec&EnableRedirection=$arg_EnableRedirection&EnableRemoteMedia=$arg_EnableRemoteMedia&StartTimeTicks=$arg_StartTimeTicks";
        proxy_cache_key "$cache_key&slicerange=$slice_range";
    }

    access_log off;
    error_log off;
}

CloudFlare

众所周知,Cloudflare不允许你的视频流大量经过他们的服务器,即使你设置了绕过缓存,也算流量经过Cloudflare服务器:Section 2.8 of the Self-Serve Subscription Agreement (TOS)

图片[1]-[Emby]使用nginx反向代理并缓存视频流-DoubleWorld’s

但是我们希望html,css,js等文件经过Cloudflare的路由优化和缓存,那怎么办呢?

解决办法

我们可以使用两个域名,主域名开启CDN代理,次域名不开启代理,当使用主域名访问EMBY的视频流文件使,使用307重定向到次域名即可。

准备工作:

主域名 emby.domain.com 开启cdn,次域名 src-emby.domain.com 不开启cdn

主/次域名均已经使用Nginx反向代理到EMBY服务器

Cloudflare的速度->优化->Auto Minify功能全部关闭,否则会导致各种意想不到的Bug

Cloudflare的速度->优化->Rocket Loader™也要关掉,不然会导致很多js莫名其妙的Bug

反向代理Emby后如果启用了https,则需要在emby的 网络 -> 安全连接模式 选项中选择由反向代理处理

开启cdn后可正常访问emby服务器

使用Cloudflare的重定向规则实现重定向

打开Cloudflare规则->重定向规则,新建一个规则,规则名称随意

规则填写(替换域名即可),注意大小写:

图片[2]-[Emby]使用nginx反向代理并缓存视频流-DoubleWorld’s
#表达式预览(可以复制粘贴到Cloudflare)
(http.host eq "emby.domain.com" and starts_with(http.request.uri.path, "/emby/videos")) or (http.host eq "emby.domain.com" and starts_with(http.request.uri.path, "/emby/Items") and ends_with(http.request.uri.path, "/Download"))
图片[3]-[Emby]使用nginx反向代理并缓存视频流-DoubleWorld’s
concat("https://src-", http.host, http.request.uri)

保存后,所有视频流的请求都会307重定向到直连源服务器的地址,就不用担心被Cloudflare查封了。

Cloudflare缓存配置

进入Cloudflare 缓存 -> Cache Rules

创建一个规则,传入规则如下:

图片[4]-[Emby]使用nginx反向代理并缓存视频流-DoubleWorld’s

缓存状态 选 符合缓存条件

边缘 TTL 选 接受源服务器

状态代码 TTL 分别设置为 单一代码 – 200/206 – 一年

缓存键 开启 缓存欺骗盔甲 和 启用查询字符串排序

测试

随便点开一个视频播放几秒,然后查看缓存文件夹内有没有生成文件即可

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容