emby增加代理模式
This commit is contained in:
+26
-19
@@ -3430,6 +3430,7 @@ const EmbyConfigComponent = ({
|
|||||||
removeEmbyPrefix: false,
|
removeEmbyPrefix: false,
|
||||||
appendMediaSourceId: false,
|
appendMediaSourceId: false,
|
||||||
transcodeMp4: false,
|
transcodeMp4: false,
|
||||||
|
proxyPlay: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 从配置加载源列表
|
// 从配置加载源列表
|
||||||
@@ -3468,6 +3469,7 @@ const EmbyConfigComponent = ({
|
|||||||
removeEmbyPrefix: false,
|
removeEmbyPrefix: false,
|
||||||
appendMediaSourceId: false,
|
appendMediaSourceId: false,
|
||||||
transcodeMp4: false,
|
transcodeMp4: false,
|
||||||
|
proxyPlay: false,
|
||||||
});
|
});
|
||||||
setEditingSource(null);
|
setEditingSource(null);
|
||||||
setShowAddForm(false);
|
setShowAddForm(false);
|
||||||
@@ -3901,25 +3903,6 @@ const EmbyConfigComponent = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 默认源开关 */}
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<label className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
|
||||||
设为默认源
|
|
||||||
</label>
|
|
||||||
<button
|
|
||||||
onClick={() => setFormData({ ...formData, isDefault: !formData.isDefault })}
|
|
||||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
|
||||||
formData.isDefault ? 'bg-blue-600' : 'bg-gray-200 dark:bg-gray-700'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
|
||||||
formData.isDefault ? 'translate-x-6' : 'translate-x-1'
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 高级选项 */}
|
{/* 高级选项 */}
|
||||||
<div className='border-t border-gray-200 dark:border-gray-700 pt-4 mt-4'>
|
<div className='border-t border-gray-200 dark:border-gray-700 pt-4 mt-4'>
|
||||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300 mb-3'>
|
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300 mb-3'>
|
||||||
@@ -3997,6 +3980,30 @@ const EmbyConfigComponent = ({
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 视频播放代理开关 */}
|
||||||
|
<div className='flex items-center justify-between py-3 border-b border-gray-200 dark:border-gray-700'>
|
||||||
|
<div className='flex-1'>
|
||||||
|
<h4 className='text-sm font-medium text-gray-900 dark:text-white'>
|
||||||
|
视频播放代理
|
||||||
|
</h4>
|
||||||
|
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||||
|
启用后视频播放将通过服务器代理
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setFormData({ ...formData, proxyPlay: !formData.proxyPlay })}
|
||||||
|
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||||||
|
formData.proxyPlay ? 'bg-blue-600' : 'bg-gray-200 dark:bg-gray-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||||
|
formData.proxyPlay ? 'translate-x-6' : 'translate-x-1'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 操作按钮 */}
|
{/* 操作按钮 */}
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ export async function GET(
|
|||||||
// 获取 Emby 客户端
|
// 获取 Emby 客户端
|
||||||
let client = await getEmbyClient(embyKey);
|
let client = await getEmbyClient(embyKey);
|
||||||
|
|
||||||
// 构建 Emby 原始播放链接
|
// 构建 Emby 原始播放链接(强制获取直接URL,避免代理循环)
|
||||||
let embyStreamUrl = await client.getStreamUrl(itemId);
|
let embyStreamUrl = await client.getStreamUrl(itemId, true, true);
|
||||||
|
|
||||||
// 构建请求头,转发 Range 请求
|
// 构建请求头,转发 Range 请求
|
||||||
const requestHeaders: HeadersInit = {};
|
const requestHeaders: HeadersInit = {};
|
||||||
@@ -81,7 +81,7 @@ export async function GET(
|
|||||||
const { embyManager } = await import('@/lib/emby-manager');
|
const { embyManager } = await import('@/lib/emby-manager');
|
||||||
embyManager.clearCache();
|
embyManager.clearCache();
|
||||||
client = await getEmbyClient(embyKey);
|
client = await getEmbyClient(embyKey);
|
||||||
embyStreamUrl = await client.getStreamUrl(itemId);
|
embyStreamUrl = await client.getStreamUrl(itemId, true, true);
|
||||||
videoResponse = await fetch(embyStreamUrl, {
|
videoResponse = await fetch(embyStreamUrl, {
|
||||||
headers: requestHeaders,
|
headers: requestHeaders,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ export interface AdminConfig {
|
|||||||
removeEmbyPrefix?: boolean; // 播放链接移除/emby前缀
|
removeEmbyPrefix?: boolean; // 播放链接移除/emby前缀
|
||||||
appendMediaSourceId?: boolean; // 拼接MediaSourceId参数
|
appendMediaSourceId?: boolean; // 拼接MediaSourceId参数
|
||||||
transcodeMp4?: boolean; // 转码mp4
|
transcodeMp4?: boolean; // 转码mp4
|
||||||
|
proxyPlay?: boolean; // 视频播放代理开关
|
||||||
}>;
|
}>;
|
||||||
// 旧格式:单源配置(向后兼容)
|
// 旧格式:单源配置(向后兼容)
|
||||||
Enabled?: boolean;
|
Enabled?: boolean;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ interface EmbySourceConfig {
|
|||||||
removeEmbyPrefix?: boolean;
|
removeEmbyPrefix?: boolean;
|
||||||
appendMediaSourceId?: boolean;
|
appendMediaSourceId?: boolean;
|
||||||
transcodeMp4?: boolean;
|
transcodeMp4?: boolean;
|
||||||
|
proxyPlay?: boolean; // 视频播放代理开关
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmbyManager {
|
class EmbyManager {
|
||||||
@@ -182,6 +183,7 @@ export function migrateEmbyConfig(config: AdminConfig): AdminConfig {
|
|||||||
removeEmbyPrefix: false,
|
removeEmbyPrefix: false,
|
||||||
appendMediaSourceId: false,
|
appendMediaSourceId: false,
|
||||||
transcodeMp4: false,
|
transcodeMp4: false,
|
||||||
|
proxyPlay: false,
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-1
@@ -11,6 +11,8 @@ interface EmbyConfig {
|
|||||||
removeEmbyPrefix?: boolean;
|
removeEmbyPrefix?: boolean;
|
||||||
appendMediaSourceId?: boolean;
|
appendMediaSourceId?: boolean;
|
||||||
transcodeMp4?: boolean;
|
transcodeMp4?: boolean;
|
||||||
|
proxyPlay?: boolean; // 视频播放代理开关
|
||||||
|
key?: string; // Emby源的唯一标识
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EmbyItem {
|
interface EmbyItem {
|
||||||
@@ -71,6 +73,8 @@ export class EmbyClient {
|
|||||||
private removeEmbyPrefix: boolean;
|
private removeEmbyPrefix: boolean;
|
||||||
private appendMediaSourceId: boolean;
|
private appendMediaSourceId: boolean;
|
||||||
private transcodeMp4: boolean;
|
private transcodeMp4: boolean;
|
||||||
|
private proxyPlay: boolean;
|
||||||
|
private embyKey?: string;
|
||||||
|
|
||||||
constructor(config: EmbyConfig) {
|
constructor(config: EmbyConfig) {
|
||||||
let serverUrl = config.ServerURL.replace(/\/$/, '');
|
let serverUrl = config.ServerURL.replace(/\/$/, '');
|
||||||
@@ -79,6 +83,8 @@ export class EmbyClient {
|
|||||||
this.removeEmbyPrefix = config.removeEmbyPrefix || false;
|
this.removeEmbyPrefix = config.removeEmbyPrefix || false;
|
||||||
this.appendMediaSourceId = config.appendMediaSourceId || false;
|
this.appendMediaSourceId = config.appendMediaSourceId || false;
|
||||||
this.transcodeMp4 = config.transcodeMp4 || false;
|
this.transcodeMp4 = config.transcodeMp4 || false;
|
||||||
|
this.proxyPlay = config.proxyPlay || false;
|
||||||
|
this.embyKey = config.key;
|
||||||
|
|
||||||
// 如果 URL 不包含 /emby 路径,自动添加(除非启用了 removeEmbyPrefix)
|
// 如果 URL 不包含 /emby 路径,自动添加(除非启用了 removeEmbyPrefix)
|
||||||
if (!serverUrl.endsWith('/emby') && !this.removeEmbyPrefix) {
|
if (!serverUrl.endsWith('/emby') && !this.removeEmbyPrefix) {
|
||||||
@@ -461,8 +467,27 @@ export class EmbyClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getStreamUrl(itemId: string, direct: boolean = true): Promise<string> {
|
async getStreamUrl(itemId: string, direct: boolean = true, forceDirectUrl: boolean = false): Promise<string> {
|
||||||
const token = this.apiKey || this.authToken;
|
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;
|
let url: string;
|
||||||
|
|
||||||
if (direct) {
|
if (direct) {
|
||||||
|
|||||||
Reference in New Issue
Block a user