# go-emby2openlist
**Repository Path**: arongwang_admin/go-emby2openlist
## Basic Information
- **Project Name**: go-emby2openlist
- **Description**: 代理emby上openlist的网盘请求到直链
- **Primary Language**: Go
- **License**: GPL-3.0
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2026-06-30
- **Last Updated**: 2026-06-30
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
go-emby2openlist
Go 语言编写的 Emby + OpenList 网盘直链反向代理,深度适配阿里云盘转码播放。
---
## 目录
- [关于本仓库](#关于本仓库)
- [本仓库更新说明](#本仓库更新说明)
- [工作原理](#工作原理)
- [功能特性](#功能特性)
- [管理控制台](#管理控制台)
- [客户端兼容性](#客户端兼容性)
- [环境准备](#环境准备)
- [部署安装](#部署安装)
- [SSL 配置](#ssl-配置)
- [自定义 Web 脚本与样式](#自定义-web-脚本与样式)
- [OpenList 本地目录树](#openlist-本地目录树)
- [开放 API](#开放-api)
- [后续计划](#后续计划)
---
## 关于本仓库
本仓库基于上游项目 [AmbitiousJun/go-emby2openlist](https://github.com/AmbitiousJun/go-emby2openlist) **v2.8.0** 维护,在保留原有功能的基础上,针对高并发播放与目录树同步场景做了性能优化和稳定性修复。
| 项目 | 地址 |
| --- | --- |
| 上游项目 | https://github.com/AmbitiousJun/go-emby2openlist |
| 本仓库 (GitHub) | https://github.com/ArongWang/go-emby2openlist |
| 本仓库 (Gitee) | https://gitee.com/arongwang_admin/go-emby2openlist |
> 若你需要功能更完善的通用方案,可参考 [bpking1/embyExternalUrl](https://github.com/bpking1/embyExternalUrl)。
---
## 本仓库更新说明
相比上游 v2.8.0,本仓库在高并发播放、混合媒体库、目录树同步等场景下做了针对性优化。以下按模块说明实现细节与预期效果。
### 一、缓存体系优化
#### 1. PlaybackInfo 路径缓存
播放直链前需先向 Emby 查询媒体本地路径(PlaybackInfo)。多客户端同时点播时,同一媒体会被重复请求,造成 Emby 源站压力。
| 项目 | 说明 |
| --- | --- |
| 缓存 Key | `itemId` + `MediaSourceId` + `api_key` 组合 |
| TTL | 10 分钟 |
| 并发去重 | 使用 `singleflight`,同一 Key 的并发请求只回源一次 |
| 容量上限 | 16,384 条,超出后清理过期条目 |
| 生效位置 | 直链重定向(`/stream`、`/original`)等流程 |
#### 2. 302 直链重定向轻量缓存
网盘直链获取通常返回 302 跳转,响应体为空。若与 API 响应共用 body 缓存,会挤占配额且收益有限。
- 独立维护最多 **16,384** 条 302 缓存,仅保存 `Location`,不占用 body 缓存配额(总上限 100MB / 8092 条)
- 命中时直接重定向,跳过 OpenList 重复请求
- 容量满时按过期时间批量淘汰至上限的 90%,降低频繁扫描开销
- Strm 远程地址重定向默认缓存 10 分钟
#### 3. 异步 body 缓存写入
上游在请求 goroutine 中同步写入缓存,大响应会阻塞后续处理。
| 项目 | 说明 |
| --- | --- |
| Worker 数量 | 4 个固定协程 |
| 任务队列 | 512 条,非阻塞入队 |
| 队列满时 | 丢弃最旧任务后重试;仍失败则放弃写入 |
| 设计原则 | **永不阻塞请求 goroutine**,仅影响命中率,不影响正确性 |
#### 4. 流式响应跳过 body 缓存
本地媒体库、Emby Web 静态资源等经 `ProxyOrigin` 回源时,响应体可能很大(视频流、JS/CSS 等)。若全部缓冲进内存,高并发下易 OOM。
- 响应体超过 **16MB** 时停止缓冲,标记 `skipCache`
- 响应头 `Expired: -1` 的透传请求(交还 Emby 原逻辑)不缓冲 body
- 不在 OpenList 代理范围内的媒体走 `passThroughToEmby`,同样不参与缓存
---
### 二、代理路由精确化
#### 问题背景
上游对所有媒体路径统一尝试 OpenList 直链代理。当 Emby 媒体库同时包含**网盘挂载路径**与**本地/Web 资源**时,Web 静态文件、本地库媒体会被误走代理逻辑,导致播放异常或多余 OpenList 请求。
#### 解决方案:`NeedsOpenlistProxy`
新增统一判断函数,只有以下路径才走 OpenList 直链:
| 条件 | 处理方式 |
| --- | --- |
| `local-media-roots` 配置的本地媒体 | 交还 Emby 原逻辑 |
| 远程 Strm 地址 | 按 Strm 规则重定向 |
| 不在 `mount-path` 且不在 `path.emby2openlist` 映射范围内 | 交还 Emby 原逻辑 |
| 命中 `mount-path` 或 `emby2openlist` 前缀 | 走 OpenList 直链 |
**路径前缀匹配**使用 `HasPathPrefix`(分隔符感知),例如 `/data/local` 不会误匹配 `/data/local-cloud`。
**交还 Emby 的处理方式:**
- `/stream`、`/universal` 请求 → 302 重定向到 `/original`,由 Emby 原生链路播放
- 其他请求 → 设置 `Expired: -1` 后 `ProxyOrigin` 透传回源
上述逻辑已应用于 `PlaybackInfo` 改写、`Redirect2OpenlistLink`、`ProxyOriginalResource` 等入口。
---
### 三、OpenList API 与目录树同步调度
#### 1. 客户端 API 优先级计数
目录树同步需要大量调用 OpenList `fs/list`、`fs/get` 等接口,与客户端播放争用同一 OpenList 实例,高峰期会拖慢起播。
| 项目 | 说明 |
| --- | --- |
| 计入优先级的请求 | 仅 `fs/list`、`fs/get`、`fs/other`(客户端播放相关) |
| 不计入的请求 | Emby 回源、静态资源、PlaybackInfo 代理等普通 HTTP |
| 繁忙阈值 | 并发 ≥ **3** 时判定为繁忙 |
| Walk 行为 | 繁忙时暂停目录树分页遍历,降至阈值以下后继续 |
#### 2. 相对空闲窗口同步
不要求 OpenList 完全零请求,而是等待连续 **5 秒**内 API 并发低于阈值,即启动/继续目录树同步。这样在持续有零星播放的服务器上,同步仍能推进。
定时同步(`refresh-interval`)每次触发前同样等待相对空闲。
#### 3. 目录树 Walk 容错
| 场景 | 上游行为 | 本仓库行为 |
| --- | --- | --- |
| InitSnapshot 本地磁盘 Walk 单路径失败 | 可能直接退出进程 | 记录日志并 `SkipDir`,继续扫描 |
| OpenList 某子目录 list 失败 | 同步中断 | `markSkippedDir` 标记,跳过该目录继续 |
| 本地目录创建失败 | — | 跳过并标记,避免误删已有缓存 |
Walk 过程中每遍历 **128** 个本地路径会检查 OpenList 负载,繁忙时主动让路。
---
### 四、稳定性修复
#### HTTP 重定向连接池泄漏
跟随 302 链时,中间响应的 body 未读取会导致 TCP 连接无法归还连接池。现已在每次跳转前用 `io.Copy(Discard)` 排空 body 再关闭。
#### m3u8 预处理通道 WaitGroup
转码 playlist 异步预热通道满时会淘汰队首任务,但未处理的被淘汰任务未调用 `Done()`,导致 `WaitGroup` 永久阻塞。淘汰时已补充 `Done()` 补偿。
---
### 五、优化效果概览
| 场景 | 预期改善 |
| --- | --- |
| 多客户端同时播放同一资源 | PlaybackInfo 回源次数显著减少 |
| 频繁拖动进度 / 切换清晰度 | 302 直链缓存减少 OpenList 重复请求 |
| 混合本地库 + 网盘库 | 本地/Web 资源不再误走 OpenList 代理 |
| 刮库 + 播放同时进行 | 播放优先,目录树在空闲窗口推进 |
| 大文件本地库回源播放 | 不再缓冲超大响应体,内存更稳定 |
| OpenList 个别目录临时不可用 | 同步跳过失败目录,服务不中断 |
---
### 继承自 v2.8.0 的功能
- **ge2o Web 管理控制台**:配置查看、日志实时查看、目录树手动更新
- Emby 首页快捷入口按钮(可在 `ge2o.web.disable-emby-btn` 关闭)
---
## 工作原理
### 传统挂载模式
```
客户端 → Emby → 磁盘挂载 (rclone/cd2) → OpenList → 网盘
```
视频数据经服务器中转,播放速度受限于服务器上行带宽与性能。
### 直链反代模式
```
API 请求: 客户端 → 反代 → Emby 源服务器 → 返回(可缓存)
视频播放: 客户端 → 反代 → OpenList → 网盘直链 → 客户端直连网盘
```
播放阶段客户端直接从网盘拉流,不再消耗服务器流量;加载速度取决于网盘会员与客户端性能。
---
## 功能特性
- OpenList 网盘原画 / Strm 直链播放
- 阿里云盘转码直链播放(不消耗三方流量包)
- OpenList 本地目录树生成(Strm / 虚拟文件 / 音乐虚拟文件)
- WebSocket 代理、客户端防转码(转容器)
- 缓存中间件(大接口、直链、字幕)
- 自定义 Web JS / CSS 注入
- ge2o Web 管理控制台
**转码直链说明**
| 项目 | 说明 |
| --- | --- |
| 三方流量包 | 不消耗 |
| 非会员限速 | 需自行测试 |
| 多音轨 | 仅默认音轨 |
| 内封字幕 | 会丢失;转码字幕会写入 PlaybackInfo |

**字幕缓存说明**
- 字幕缓存固定 30 天
- 首次播放带字幕视频时,Emby 可能从本地挂载文件提取字幕(较慢,消耗服务器流量)
- 建议使用第三方播放器(如 MX Player、Fileball)可规避此问题
**缓存时间**
| 类型 | 时长 |
| --- | --- |
| 直链 | 10 分钟(兼容阿里云盘) |
| 字幕 | 30 天 |
| PlaybackInfo(转码) | 12 小时 |
---
## 管理控制台
v2.8.0 起内置 ge2o Web 管理台,部署后访问 `http://服务器IP:8095` 即可使用。
| 页面 | 功能 |
| --- | --- |
| 首页 | 快速导航、项目信息 |
| 日志 | WebSocket 实时查看服务日志 |
| 目录树 API | 可视化调用本地目录树更新接口 |
| 设置 | 查看当前配置 |
**相关配置**(`config.yml` → `ge2o`):
```yaml
ge2o:
api-secret: my-secret # 开放 API 密钥,必填
web:
disable: false # 设为 true 禁用 Web 管理台
disable-emby-btn: false # 设为 true 隐藏 Emby 首页快捷按钮
```
---
## 客户端兼容性
| 客户端 | 版本 | 原画 | 阿里转码 | 备注 |
| --- | --- | --- | --- | --- |
| Gemby | v2.6.4 | ✅ | ✅ | — |
| Emby Web | 4.8.8.0 | ✅ | ✅ | 转码字幕偶发挂载失败 |
| Emby for Android | 3.4.23 | ✅ | ✅ | — |
| Emby for AndroidTV | 2.0.95g | ✅ | ✅ | 调进度可能触发限频;无法挂载字幕 |
| Fileball | — | ✅ | ✅ | — |
| Infuse | — | ✅ | ❌ | 缓存设为「不缓存」可避免限频 |
| VidHub | ≤ 1.0.7 | ✅ | ✅ | — |
| Stream Music | 1.3.8 | ✅ | — | — |
| Emby for Kodi Next Gen | 11.1.13 | ✅ | ✅ | 需开启 prores 转码;无法挂载字幕 |
| Emby for iOS / macOS | — | ❓ | ❓ | 未充分测试 |
---
## 环境准备
1. 已有 Emby、OpenList 服务
2. Emby 媒体库路径与 OpenList 挂载路径可对应(前缀不一致可通过 `path.emby2openlist` 映射)
3. 中间挂载服务将网盘挂载到本地磁盘(推荐 [CloudDrive2](https://www.clouddrive2.com/) 或 [rclone](https://rclone.org/))
- 阿里云盘推荐 CD2,缓存大小设为极小值(如 1MB)以减少刮削流量
- ⚠️ 不建议通过 OpenList WebDAV 挂载,Token 失效会导致 Emby 元数据丢失
4. 已安装 Docker(源码构建还需 Git)
---
## 部署安装
```shell
git clone https://github.com/ArongWang/go-emby2openlist.git
cd go-emby2openlist
cp config-example.yml config.yml
# 编辑 config.yml,参照下方路径映射示例
docker-compose up -d --build
```
访问 `http://服务器IP:8095` 验证服务。自定义端口时修改 `docker-compose.yml` 中的端口映射即可。

**常用命令**
```shell
# 查看日志
docker logs -f go-emby2openlist -n 1000
# 修改配置后重启
docker-compose restart
# 更新版本
docker-compose down && git pull && docker-compose up -d --build
# 清理旧镜像
docker image prune -f
```
> 首次部署可参考[核心配置说明](https://github.com/AmbitiousJun/go-emby2openlist/issues/108#issuecomment-2928599051)先跑通,再按需补充其他项。更新后请对比 `config-example.yml` 是否有新增配置。
---
## SSL 配置
1. 将证书与私钥放入 `ssl/` 目录
2. 在 `config.yml` 的 `ssl` 节配置文件名
容器内端口固定:HTTP `8095`,HTTPS `8094`。自定义对外端口时在 `docker-compose.yml` 中修改映射即可。
---
## 自定义 Web 脚本与样式
| 类型 | 目录 | 格式 |
| --- | --- | --- |
| JS 脚本 | `custom-js/` | `.js` 文件,或写入远程 URL |
| CSS 样式 | `custom-css/` | `.css` 文件,或写入远程 URL |
放入文件后重启服务生效。多文件请统一使用 UTF-8 编码。
**常用脚本示例**
| 功能 | 链接 |
| --- | --- |
| 外部播放器按钮 | [ExternalPlayers.js](https://emby-external-url.7o7o.cc/embyWebAddExternalUrl/embyLaunchPotplayer.js) |
| 首页轮播图 | [emby-swiper.js](https://raw.githubusercontent.com/newday-life/emby-web-mod/refs/heads/main/emby-swiper/emby-swiper.js) |
| 键盘音量控制 | [audio-keyboard.js](https://github.com/AmbitiousJun/emby-css-js/blob/main/custom-js/audio-keyboard.js) |
---
## OpenList 本地目录树
监控 OpenList 目录变更,在本地生成对应目录结构供 Emby 扫描入库,支持 Strm、虚拟文件、音乐虚拟文件三种模式。
> ⚠️ 开启 ffmpeg 元数据提取存在风控风险,请谨慎使用。
### 快速启用
1. 配置 `openlist.local-tree-gen`(见 `config-example.yml`)
2. 映射容器目录 `/app/openlist-local-tree` 和 `/app/lib` 到宿主机
3. 将宿主机目录树路径挂载到 Emby 容器并扫描入库
### 三种生成模式
**1. Strm 文件** — 速度快,无 ffmpeg
```yaml
openlist:
local-tree-gen:
enable: true
strm-containers: mp4,mkv,mp3,flac
```
**2. 虚拟文件** — 生成同名空文件,默认时长 3 小时
```yaml
openlist:
local-tree-gen:
enable: true
virtual-containers: mp4,mkv
ffmpeg-enable: true # 可选,解析真实时长(有风控风险)
```
**3. 音乐虚拟文件** — 提取标签与时长,必须开启 ffmpeg
```yaml
openlist:
local-tree-gen:
enable: true
ffmpeg-enable: true
music-containers: mp3,flac
```
### 其他配置项
| 配置项 | 说明 | 示例 |
| --- | --- | --- |
| `auto-remove-max-count` | 批量删除上限,建议为总文件数的 3/4 | `6000` |
| `refresh-interval` | 刷新间隔(分钟) | `60` |
| `scan-prefixes` | 扫描前缀,空则全量 | `/电影` |
| `allow-containers` | 容器白名单 | `ass,srt,sub` |
> ffmpeg 不包含在 Docker 镜像中。首次开启 `ffmpeg-enable` 后程序会自动下载,请勿中断容器。
---
## 开放 API
### 手动更新本地目录树
```
POST /ge2o/openlist/local_tree/update
Content-Type: application/json
```
**请求体**
```json
{
"secret": "my-secret",
"prefix": "/音乐1/陈楚生",
"refresh": false
}
```
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `secret` | string | ✅ | 与 `ge2o.api-secret` 一致 |
| `prefix` | string | | 路径前缀,须为完整子目录(如 `/A/B (2026)` 而非 `/A/B`) |
| `refresh` | boolean | | 是否强制刷新 OpenList 数据,默认 `false` |
**响应**
HTTP 200,通过 body 判断结果:
```json
{ "success": false, "message": "密钥错误" }
```
也可在 Web 管理台的「目录树 API」页面可视化调用。
---
## 后续计划
| 计划 | 说明 |
| --- | --- |
| 多 OpenList 网盘支持 | 当前仅支持配置单个 OpenList 实例。后续计划支持同时接入多个 OpenList 服务(如阿里云盘、115、夸克等不同网盘),按路径前缀或媒体库自动路由到对应实例,便于混合挂载场景统一管理 |
---
## Star History