From f1707f8fa4e3ce260fb01c601d411cbb60a16e8e Mon Sep 17 00:00:00 2001 From: mtvpls Date: Fri, 9 Jan 2026 22:34:29 +0800 Subject: [PATCH] =?UTF-8?q?emby=E5=A2=9E=E5=8A=A0=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/admin/page.tsx | 45 +++++++++++-------- .../api/emby/play/[token]/[filename]/route.ts | 6 +-- src/lib/admin.types.ts | 1 + src/lib/emby-manager.ts | 2 + src/lib/emby.client.ts | 27 ++++++++++- 5 files changed, 58 insertions(+), 23 deletions(-) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 4080fae..21b4d3c 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -3430,6 +3430,7 @@ const EmbyConfigComponent = ({ removeEmbyPrefix: false, appendMediaSourceId: false, transcodeMp4: false, + proxyPlay: false, }); // 从配置加载源列表 @@ -3468,6 +3469,7 @@ const EmbyConfigComponent = ({ removeEmbyPrefix: false, appendMediaSourceId: false, transcodeMp4: false, + proxyPlay: false, }); setEditingSource(null); setShowAddForm(false); @@ -3901,25 +3903,6 @@ const EmbyConfigComponent = ({ - {/* 默认源开关 */} -
- - -
- {/* 高级选项 */}

@@ -3997,6 +3980,30 @@ const EmbyConfigComponent = ({ />

+ + {/* 视频播放代理开关 */} +
+
+

+ 视频播放代理 +

+

+ 启用后视频播放将通过服务器代理 +

+
+ +
{/* 操作按钮 */} diff --git a/src/app/api/emby/play/[token]/[filename]/route.ts b/src/app/api/emby/play/[token]/[filename]/route.ts index e0c8362..7518487 100644 --- a/src/app/api/emby/play/[token]/[filename]/route.ts +++ b/src/app/api/emby/play/[token]/[filename]/route.ts @@ -60,8 +60,8 @@ export async function GET( // 获取 Emby 客户端 let client = await getEmbyClient(embyKey); - // 构建 Emby 原始播放链接 - let embyStreamUrl = await client.getStreamUrl(itemId); + // 构建 Emby 原始播放链接(强制获取直接URL,避免代理循环) + let embyStreamUrl = await client.getStreamUrl(itemId, true, true); // 构建请求头,转发 Range 请求 const requestHeaders: HeadersInit = {}; @@ -81,7 +81,7 @@ export async function GET( const { embyManager } = await import('@/lib/emby-manager'); embyManager.clearCache(); client = await getEmbyClient(embyKey); - embyStreamUrl = await client.getStreamUrl(itemId); + embyStreamUrl = await client.getStreamUrl(itemId, true, true); videoResponse = await fetch(embyStreamUrl, { headers: requestHeaders, }); diff --git a/src/lib/admin.types.ts b/src/lib/admin.types.ts index e901fd7..6e2c32f 100644 --- a/src/lib/admin.types.ts +++ b/src/lib/admin.types.ts @@ -178,6 +178,7 @@ export interface AdminConfig { removeEmbyPrefix?: boolean; // 播放链接移除/emby前缀 appendMediaSourceId?: boolean; // 拼接MediaSourceId参数 transcodeMp4?: boolean; // 转码mp4 + proxyPlay?: boolean; // 视频播放代理开关 }>; // 旧格式:单源配置(向后兼容) Enabled?: boolean; diff --git a/src/lib/emby-manager.ts b/src/lib/emby-manager.ts index f00df99..548a140 100644 --- a/src/lib/emby-manager.ts +++ b/src/lib/emby-manager.ts @@ -22,6 +22,7 @@ interface EmbySourceConfig { removeEmbyPrefix?: boolean; appendMediaSourceId?: boolean; transcodeMp4?: boolean; + proxyPlay?: boolean; // 视频播放代理开关 } class EmbyManager { @@ -182,6 +183,7 @@ export function migrateEmbyConfig(config: AdminConfig): AdminConfig { removeEmbyPrefix: false, appendMediaSourceId: false, transcodeMp4: false, + proxyPlay: false, }], }; } diff --git a/src/lib/emby.client.ts b/src/lib/emby.client.ts index 52e4f22..dbde767 100644 --- a/src/lib/emby.client.ts +++ b/src/lib/emby.client.ts @@ -11,6 +11,8 @@ interface EmbyConfig { removeEmbyPrefix?: boolean; appendMediaSourceId?: boolean; transcodeMp4?: boolean; + proxyPlay?: boolean; // 视频播放代理开关 + key?: string; // Emby源的唯一标识 } interface EmbyItem { @@ -71,6 +73,8 @@ export class EmbyClient { private removeEmbyPrefix: boolean; private appendMediaSourceId: boolean; private transcodeMp4: boolean; + private proxyPlay: boolean; + private embyKey?: string; constructor(config: EmbyConfig) { let serverUrl = config.ServerURL.replace(/\/$/, ''); @@ -79,6 +83,8 @@ export class EmbyClient { this.removeEmbyPrefix = config.removeEmbyPrefix || false; this.appendMediaSourceId = config.appendMediaSourceId || false; this.transcodeMp4 = config.transcodeMp4 || false; + this.proxyPlay = config.proxyPlay || false; + this.embyKey = config.key; // 如果 URL 不包含 /emby 路径,自动添加(除非启用了 removeEmbyPrefix) if (!serverUrl.endsWith('/emby') && !this.removeEmbyPrefix) { @@ -461,8 +467,27 @@ export class EmbyClient { } } - async getStreamUrl(itemId: string, direct: boolean = true): Promise { + async getStreamUrl(itemId: string, direct: boolean = true, forceDirectUrl: boolean = false): Promise { const token = this.apiKey || this.authToken; + + // 如果启用了代理播放且不是强制获取直接URL,返回代理URL + if (this.proxyPlay && !forceDirectUrl) { + // 使用固定的token占位符,实际验证在服务端进行 + const subscribeToken = 'proxy'; + const filename = this.transcodeMp4 ? 'video.mp4' : 'video'; + + // 构建代理URL(相对路径) + let proxyUrl = `/api/emby/play/${subscribeToken}/${filename}?itemId=${itemId}`; + + // 如果有embyKey,添加到查询参数 + if (this.embyKey) { + proxyUrl += `&embyKey=${this.embyKey}`; + } + + return proxyUrl; + } + + // 原有的直接播放逻辑 let url: string; if (direct) {