// author: @bpking https://github.com/bpking1/embyExternalUrl
// 查看日志: "docker logs -f -n 10 emby-nginx 2>&1 | grep js:"
// 正常情况下此文件所有内容不需要更改
import config from "./constant.js";
import util from "./common/util.js";
import events from "./common/events.js";
import embyApi from "./api/emby-api.js";
async function redirect2Pan(r) {
events.njsOnExit(r.uri);
const ua = r.headersIn["User-Agent"];
r.warn(`redirect2Pan, UA: ${ua}`);
// check route cache
const routeCacheConfig = config.routeCacheConfig;
if (routeCacheConfig.enable) {
let cacheKey = util.parseExpression(r, routeCacheConfig.keyExpression);
const cacheLevle = r.args[util.args.cacheLevleKey] ?? util.chcheLevelEnum.L1;
let routeDictKey = "routeL1Dict";
if (util.chcheLevelEnum.L2 === cacheLevle) {
routeDictKey = "routeL2Dict";
// } else if (util.chcheLevelEnum.L3 === cacheLevle) {
// routeDictKey = "routeL3Dict";
}
let cachedLink = ngx.shared[routeDictKey].get(cacheKey);
if (!cachedLink) {
// 115 must use ua
cacheKey += `:${ua}`;
cachedLink = ngx.shared[routeDictKey].get(cacheKey);
}
if (!!cachedLink) {
r.warn(`hit routeCache ${cacheLevle}: ${cachedLink}`);
if (cachedLink.startsWith("@")) {
// use original link
return internalRedirect(r, cachedLink, true);
} else {
return redirect(r, cachedLink, true);
}
}
}
let embyRes = {
path: r.args[util.args.filePathKey],
notLocal: r.args[util.args.notLocalKey] === "1", // fuck js Boolean("false") === true, !!"0" === true
};
if (!embyRes.path) {
// fetch mount emby/jellyfin file path
const itemInfo = util.getItemInfo(r);
r.warn(`itemInfoUri: ${itemInfo.itemInfoUri}`);
// start = Date.now();
embyRes = await util.cost(fetchEmbyFilePath,
itemInfo.itemInfoUri,
itemInfo.itemId,
itemInfo.Etag,
itemInfo.mediaSourceId);
r.log(`embyRes: ${JSON.stringify(embyRes)}`);
if (embyRes.message.startsWith("error")) {
r.error(embyRes.message);
return r.return(500, embyRes.message);
}
} else {
embyRes.path = decodeURIComponent(embyRes.path);
r.warn(`cached PlaybackInfo path, will skip excess fetchEmbyFilePath`);
}
// strm file internal text maybe encode
if (embyRes.notLocal) {
embyRes.path = decodeURIComponent(embyRes.path);
}
r.warn(`mount emby file path: ${embyRes.path}`);
// routeRule
const routeMode = util.getRouteMode(r, embyRes.path, false, embyRes.notLocal);
if (util.routeEnum.proxy == routeMode) {
// use original link
return internalRedirect(r);
} else if (util.routeEnum.block == routeMode) {
return r.return(403, "blocked");
}
let isRemote = util.checkIsRemoteByPath(embyRes.path);
// file path mapping
let embyPathMapping = config.embyPathMapping;
config.embyMountPath.map(s => {
if (!!s) {
embyPathMapping.unshift([0, 0 , s, ""]);
}
});
r.warn(`embyPathMapping: ${JSON.stringify(embyPathMapping)}`);
let embyItemPath = embyRes.path;
embyPathMapping.map(arr => {
if ((arr[1] == 0 && embyRes.notLocal)
|| (arr[1] == 1 && (!embyRes.notLocal || isRemote))
|| (arr[1] == 2 && (!embyRes.notLocal || !isRemote))) {
return;
}
embyItemPath = util.strMapping(arr[0], embyItemPath, arr[2], arr[3]);
});
isRemote = util.checkIsRemoteByPath(embyItemPath)
r.warn(`mapped emby file path: ${embyItemPath}`);
// strm file inner remote link redirect,like: http,rtsp
if (isRemote) {
const rule = util.redirectStrmLastLinkRuleFilter(embyItemPath);
if (!!rule && rule.length > 0) {
r.warn(`filePath hit redirectStrmLastLinkRule: ${JSON.stringify(rule)}`);
let directUrl = await fetchStrmLastLink(embyItemPath, rule[2], rule[3], ua);
if (!!directUrl) {
embyItemPath = directUrl;
} else {
r.warn(`warn: fetchStrmLastLink, not expected result, failback once`);
directUrl = await fetchStrmLastLink(util.strmLinkFailback(strmLink), rule[2], rule[3], ua);
if (!!directUrl) {
embyItemPath = directUrl;
}
}
}
// don't encode, excepted webClient, clients not decode
return redirect(r, embyItemPath);
}
// fetch alist direct link
const alistFilePath = embyItemPath;
const alistToken = config.alistToken;
const alistAddr = config.alistAddr;
const alistFsGetApiPath = `${alistAddr}/api/fs/get`;
const alistRes = await util.cost(fetchAlistPathApi,
alistFsGetApiPath,
alistFilePath,
alistToken,
ua,
);
r.warn(`fetchAlistPathApi, UA: ${ua}`);
if (!alistRes.startsWith("error")) {
// routeRule
const routeMode = util.getRouteMode(r, alistRes, true, embyRes.notLocal);
if (util.routeEnum.proxy == routeMode) {
// use original link
return internalRedirect(r);
} else if (util.routeEnum.block == routeMode) {
return r.return(403, "blocked");
}
return redirect(r, alistRes);
}
r.warn(`alistRes: ${alistRes}`);
if (alistRes.startsWith("error403")) {
r.error(alistRes);
return r.return(403, alistRes);
}
if (alistRes.startsWith("error500")) {
r.warn(`will req alist /api/fs/list to rerty`);
// const filePath = alistFilePath.substring(alistFilePath.indexOf("/", 1));
const filePath = alistFilePath;
const alistFsListApiPath = `${alistAddr}/api/fs/list`;
const foldersRes = await fetchAlistPathApi(
alistFsListApiPath,
"/",
alistToken,
ua,
);
if (foldersRes.startsWith("error")) {
r.error(foldersRes);
return r.return(500, foldersRes);
}
const folders = foldersRes.split(",").sort();
for (let i = 0; i < folders.length; i++) {
r.warn(`try to fetch alist path from /${folders[i]}${filePath}`);
let driverRes = await fetchAlistPathApi(
alistFsGetApiPath,
`/${folders[i]}${filePath}`,
alistToken,
ua,
);
if (!driverRes.startsWith("error")) {
driverRes = driverRes.includes("http://172.17.0.1")
? driverRes.replace("http://172.17.0.1", config.alistPublicAddr)
: driverRes;
return redirect(r, driverRes);
}
}
r.warn(`fail to fetch alist resource: not found`);
return r.return(404);
}
r.error(alistRes);
return r.return(500, alistRes);
}
// 拦截 PlaybackInfo 请求,防止客户端转码(转容器)
async function transferPlaybackInfo(r) {
events.njsOnExit(r.uri);
let start = Date.now();
// replay the request
const proxyUri = util.proxyUri(r.uri);
r.warn(`playbackinfo proxy uri: ${proxyUri}`);
const query = util.generateUrl(r, "", "").substring(1);
r.warn(`playbackinfo proxy query string: ${query}`);
const response = await r.subrequest(proxyUri, {
method: r.method,
args: query
});
const isPlayback = r.args.IsPlayback === "true";
// const deviceId = util.getDeviceId(r.args);
if (response.status === 200) {
const body = JSON.parse(response.responseText);
if (body.MediaSources && body.MediaSources.length > 0) {
r.log(`main request headersOut: ${JSON.stringify(r.headersOut)}`);
r.log(`subrequest headersOut: ${JSON.stringify(response.headersOut)}`);
r.warn(`origin playbackinfo: ${response.responseText}`);
const transcodeConfig = config.transcodeConfig; // routeRule
const routeCacheConfig = config.routeCacheConfig;
for (let i = 0; i < body.MediaSources.length; i++) {
const source = body.MediaSources[i];
// if (source.IsRemote) {
// // live streams are not blocked
// // return r.return(200, response.responseText);
// }
r.warn(`default modify direct play supports all true`);
source.SupportsDirectPlay = true;
source.SupportsDirectStream = true;
source.SupportsTranscoding = transcodeConfig.enable;
if (!source.SupportsTranscoding && !transcodeConfig.redirectTran
没有合适的资源?快使用搜索试试~ 我知道了~
温馨提示
简而言之,就是把网盘里面的视频资源,挂载到支持docker的设备上,作为一个外挂盘,使用emby来搭建一个流媒体平台,alist是开源工具,可以集成多个网盘,115,百度,阿里,夸克等国内常见网盘都支持。这里提供的就是本人在用的配置,alist集成emby使用的 https://github.com/bpking1/embyExternalUrl 的方案,如果后续不可用,可以到这个网站去更新一下最新的配置,使用者需要改写一些东西,在 nginx/conf.d/constant.js和 rclone/rclone.conf,constant.js 需要修改alist的地址和token和emby的一些信息,便于 nginx拦截器修改官方emby的响应,以及调用alist的结果来替换播放地址。rclone.conf主要是用于把alist关联的网盘挂载到本地,所以需要填入alist的地址,模板中已经填了个大概。auto_symlink主要是把网盘里面的视频文件创建一个 strm文件,提升emby扫描速度,并且也是为了避免频繁扫描网盘库,被风控。
资源推荐
资源详情
资源评论






















收起资源包目录
































共 21 条
- 1
资源评论


星尘安全
- 粉丝: 2407
上传资源 快速赚钱
我的内容管理 展开
我的资源 快来上传第一个资源
我的收益
登录查看自己的收益我的积分 登录查看自己的积分
我的C币 登录后查看C币余额
我的收藏
我的下载
下载帮助


最新资源
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈



安全验证
文档复制为VIP权益,开通VIP直接复制
