前言
国内网盘通过rclone挂载搭建的emby服务端本身速度尚可,如果你的服务器是家里的nas,这方案算是比较合适的方案。为啥要用网盘搭建emby影视库,因为网盘提供了家里硬盘所没有的大容量,如果你的影视资源比较多的话,网盘挂载应该是你的选择。
这里我提供了另一个思路,rclone挂载国内网盘,实现emby的直链播放,什么意思呢,即实现播放电影走的是网盘的cdn服务器,不走服务器流量,这样你播放电影不受限于家里nas的上传带宽,或者不影响vps的流量额度,此外由于是国内直链播放,速度相当的快,如果emby服务端是放在vps,vps到你家里的速度影响的只是前端的速度,即海报刷新的速度,不影响视频播放的速度。
当然,要实现这个方案,离不开大佬的智慧结晶,我这里其实起了一个抛砖引玉的作用,贴一下群里 bpking大佬的脚本及教程,写得比较简单,适合稍微有点基础的人。我这里记录下我自己的折腾过程,本文内容可能会比较长,请做好心里准备 。
原理
搭建 alist多种存储的目录文件列表程序 ,将需要挂载的网盘添加上去,如阿里云盘,世纪互联等,然后使用 nginx
及其 njs
模块将 emby
视频播放地址劫持到 alist
直链。
准备工作
准备一台vps,系统推荐 Debian11
,并搭建好 emby
服务端,解决 rclone
挂载国内网盘,这里不再赘述,网上教程很多。最终访问 http://vps-ip:8096/
,可以正常访问 emby
并正常播放视频,视为完成准备工作。
教程步骤
1. 安装alist并创建网盘列表
alsit
项目地址: alist项目
参照 alist文档 的安装教程,我这里采用直装版。
安装完成后,打开 http://vps-ip:5244/
,输入密码,进入后台,选择账号-添加:
![图片[1]-[Emby]基于网盘挂载的emby服务端并实现直链播放-DoubleWorld’s](https://www.ivtuber.com/wp-content/uploads/2023/11/20231122201705868-3575656713.png)
这边我rclone挂载的是名为 sp01
的世纪互联 sharepoint
,参考我的设置,其 中客户端ID
, 客户端密钥
, 刷新令牌(refresh token)
均可以在 rclone
配置中找到, sharepoint站点ID
,填你创建的site id,如果你不知道这个是什么的话,访问 获取SharePoint网站site-id 。
完成设置后,点击右下角的首页,进入 sp01
目录:
![图片[2]-[Emby]基于网盘挂载的emby服务端并实现直链播放-DoubleWorld’s](https://www.ivtuber.com/wp-content/uploads/2023/11/20231122201743859-1629609280.png)
随便点击一部电影并试试播放速度:
![图片[3]-[Emby]基于网盘挂载的emby服务端并实现直链播放-DoubleWorld’s](https://www.ivtuber.com/wp-content/uploads/2023/11/20231122201811325-351974486.png)
速度不错,并且 vps
无瞬时的大流量上传的话, alist
安装完成。
2. 安装nginx
如果你的 nginx
无其他用途,仅用来反代 emby
,推荐用 前言 大佬教程里的 docker版 ,省却了很多折腾步骤,这里我决定采用安装版。要求 nginx
版本大于 1.20
,如果你已经安装过 nginx
,可以通过以下命令查看版本:
nginx -v
版本如大于 1.20
即可,但是由于 debian
默认的 nginx
源版本往往比较低,所以我们要采用官方的安装方式: nginx官方最新版debian安装教程 。安装过程不重复了,自行参照下官网步骤。
安装njs模块
apt install nginx-module-njs
安装完成后,进入 nginx
的配置目录:
cd /etc/nginx/conf.d
创建你域名的配置,如 yourdomain.com.conf
,添加如下内容
# Load the njs script
js_path /etc/nginx/conf.d/;
js_import emby2Pan from emby.js;
#Cache images
proxy_cache_path /var/cache/nginx/emby levels=1:2 keys_zone=emby:100m max_size=1g inactive=30d use_temp_path=off;
proxy_cache_path /var/cache/nginx/emby/subs levels=1:2 keys_zone=embysubs:10m max_size=1g inactive=30d use_temp_path=off;
server{
gzip on;
listen 80;
server_name yourdomain.com;
# aliDrive direct stream need no-referrer
add_header 'Referrer-Policy' 'no-referrer';
set $emby http://127.0.0.1:8096; #emby address
# Proxy sockets traffic for jellyfin-mpv-shim and webClient
location ~ /(socket|embywebsocket) {
# Proxy Emby Websockets traffic
proxy_pass $emby;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
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;
}
# Redirect the stream to njs
location ~* /videos/(\d+)/stream {
js_content emby2Pan.redirect2Pan;
}
# for webClient download ,android is SyncService api
location ~* /Items/(\d+)/Download {
js_content emby2Pan.redirect2Pan;
}
#Cache the Subtitles
location ~* /videos/(.*)/Subtitles {
proxy_pass $emby;
proxy_set_header Host $host;
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_cache embysubs;
proxy_cache_revalidate on;
proxy_cache_lock_timeout 10s;
proxy_cache_lock on;
proxy_cache_valid 200 30d;
proxy_cache_key $proxy_host$uri;
#add_header X-Cache-Status $upstream_cache_status; # This is only to check if cache is working
}
# Cache the images
location ~ /Items/(.*)/Images {
proxy_pass $emby;
proxy_set_header Host $host;
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_cache emby;
proxy_cache_revalidate on;
proxy_cache_lock_timeout 10s;
proxy_cache_lock on;
# add_header X-Cache-Status $upstream_cache_status; # This is only to check if cache is working
}
location / {
# Proxy main Emby traffic
proxy_pass $emby;
proxy_set_header Host $host;
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;
# Disable buffering when the nginx proxy gets very resource heavy upon streaming
proxy_buffering off;
}
}
同目录下创建 emby.js
,添加如下内容:
//查看日志: "docker logs -f -n 10 emby-nginx 2>&1 | grep js:"
async function redirect2Pan(r) {
//下面4个设置,通常来说保持默认即可,根据实际情况修改
const embyHost = 'http://127.0.0.1:8096'; //这里默认emby的地址是宿主机,要注意iptables给容器放行端口
const embyMountPath = '/mnt'; // rclone 的挂载目录, 例如将od, gd挂载到/mnt目录下: /mnt/onedrive /mnt/gd ,那么这里就填写 /mnt
const alistPwd = 'alist'; //alist password
const alistApiPath = 'http://127.0.0.1:5244/api/public/path'; //访问宿主机上5244端口的alist api, 要注意iptables给容器放行端口
//fetch mount emby file path
const itemId = /[\d]+/.exec(r.uri)[0];
const mediaSourceId = r.args.MediaSourceId;
const api_key = r.args.api_key;
//infuse用户需要填写下面的api_key, 感谢@amwamw968
if ((api_key === null) || (api_key === undefined)) {
api_key = 'b19cde886a384fc097750b412345678';//这里填自己的API KEY
r.error(`api key for Infuse: ${api_key}`);
}
const itemInfoUri = `${embyHost}/emby/Items/${itemId}/PlaybackInfo?api_key=${api_key}`;
r.error(`itemInfoUri: ${itemInfoUri}`);
const embyRes = await fetchEmbyFilePath(itemInfoUri, mediaSourceId);
if (embyRes.startsWith('error')) {
r.error(embyRes);
r.return(500, embyRes);
return;
}
r.error(`mount emby file path: ${embyRes}`);
//fetch alist direct link
const alistFilePath = embyRes.replace(embyMountPath, '');
const alistRes = await fetchAlistPathApi(alistApiPath, alistFilePath, alistPwd);
if (!alistRes.startsWith('error')) {
r.error(`redirect to: ${alistRes}`);
r.return(302, alistRes);
return;
}
if (alistRes.startsWith('error401')) {
r.error(alistRes);
r.return(401, alistRes);
return;
}
if (alistRes.startsWith('error404')) {
const filePath = alistFilePath.substring(alistFilePath.indexOf('/', 1));
const foldersRes = await fetchAlistPathApi(alistApiPath, '/', alistPwd);
if (foldersRes.startsWith('error')) {
r.error(foldersRes);
r.return(500, foldersRes);
return;
}
const folders = foldersRes.split(',').sort();
for (let i = 0; i < folders.length; i++) {
r.error(`try to fetch alist path from /${folders[i]}${filePath}`);
const driverRes = await fetchAlistPathApi(alistApiPath, `/${folders[i]}${filePath}`, alistPwd);
if (!driverRes.startsWith('error')) {
r.error(`redirect to: ${driverRes}`);
r.return(302, driverRes);
return;
}
}
r.error(alistRes);
r.return(404, alistRes);
return;
}
r.error(alistRes);
r.return(500, alistRes);
return;
}
async function fetchAlistPathApi(alistApiPath, alistFilePath, alistPwd) {
const alistRequestBody = {
"path": alistFilePath,
"password": alistPwd
}
try {
const response = await ngx.fetch(alistApiPath, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
max_response_body_size: 65535,
body: JSON.stringify(alistRequestBody)
})
if (response.ok) {
const result = await response.json();
if (result === null || result === undefined) {
return `error: alist_path_api response is null`;
}
if (result.message == 'success') {
if (result.data.type == 'file') {
return result.data.files[0].url;
}
if (result.data.type == 'folder') {
return result.data.files.map(item => item.name).join(',');
}
}
if (result.code == 401) {
return `error401: alist_path_api ${result.message}`;
}
if (result.message.includes('account')) {
return `error404: alist_path_api ${result.code} ${result.message}`;
}
if (result.message == 'path not found') {
return `error404: alist_path_api ${result.message}`;
}
return `error: alist_path_api ${result.code} ${result.message}`;
}
else {
return `error: alist_path_api ${response.status} ${response.statusText}`;
}
} catch (error) {
return (`error: alist_path_api fetchAlistFiled ${error}`);
}
}
async function fetchEmbyFilePath(itemInfoUri, mediaSourceId) {
try {
const res = await ngx.fetch(itemInfoUri, { max_response_body_size: 65535 });
if (res.ok) {
const result = await res.json();
if (result === null || result === undefined) {
return `error: emby_api itemInfoUri response is null`;
}
const mediaSource = result.MediaSources.find(m => m.Id == mediaSourceId);
if (mediaSource === null || mediaSource === undefined) {
return `error: emby_api mediaSourceId ${mediaSourceId} not found`;
}
return mediaSource.Path;
}
else {
return (`error: emby_api ${res.status} ${res.statusText}`);
}
}
catch (error) {
return (`error: emby_api fetch mediaItemInfo failed, ${error}`);
}
}
export default { redirect2Pan };
根据注释的地方自行调整相应的配置。
修改 /etc/nginx/nginx.conf
,在首行添加如下内容:
load_module modules/ngx_http_js_module.so;
验证 nginx
配置是否问题:
nginx -c /etc/nginx/nginx.conf
nginx -t
如无报错,重启
nginx -s reload
打开上述 nginx
配置的域名,如http://yourdomain.com , 注意这里不要访问默认的 8096
端口,如果能正常访问 emby
界面, nginx
安装工作完成。
3. 验证直链播放是否成功
随机打开一部电影,验证播放、拖曳速度。
查看 nginx js
日志:
tail -f -n 10 /var/log/nginx/access.log /var/log/nginx/error.log | grep js
如出现以下直链地址,表示直链成功,并且此时流量不经过 vps
服务器。
![图片[4]-[Emby]基于网盘挂载的emby服务端并实现直链播放-DoubleWorld’s](https://www.ivtuber.com/wp-content/uploads/2023/11/20231122201911436-263638284.png)
- 最新
- 最热
只看作者