diff --git a/src/api/types.ts b/src/api/types.ts
index 335ed113..b0ef9a36 100644
--- a/src/api/types.ts
+++ b/src/api/types.ts
@@ -1025,6 +1025,10 @@ export interface FileItem {
export interface MediaServerPlayItem {
// ID
id?: string | number
+ // 媒体服务器项目ID
+ item_id?: string | number
+ // 媒体服务器ID
+ server_id?: string
// 标题
title: string
// 副标题
@@ -1049,6 +1053,10 @@ export interface MediaServerLibrary {
server: string
// ID
id?: string | number
+ // 媒体服务器项目ID
+ item_id?: string | number
+ // 媒体服务器ID
+ server_id?: string
// 名称
name: string
// 路径
diff --git a/src/components/cards/BackdropCard.vue b/src/components/cards/BackdropCard.vue
index 4f2c94ba..e6961a3d 100644
--- a/src/components/cards/BackdropCard.vue
+++ b/src/components/cards/BackdropCard.vue
@@ -1,7 +1,7 @@
diff --git a/src/utils/appDeepLink.ts b/src/utils/appDeepLink.ts
index 0953bca1..1eca09db 100644
--- a/src/utils/appDeepLink.ts
+++ b/src/utils/appDeepLink.ts
@@ -64,6 +64,104 @@ interface DoubanAppParams {
fallbackUrl?: string
}
+// 媒体服务器卡片跳转所需的最小字段集合
+interface MediaServerLinkTarget {
+ id?: string | number
+ item_id?: string | number
+ itemId?: string | number
+ server_id?: string
+ serverId?: string
+ link?: string
+ server_type?: string
+}
+
+/**
+ * 判断链接参数是否为有效值。
+ * @param value 待检查的链接参数
+ */
+function getValidLinkValue(value?: string | number | null): string | null {
+ if (value === undefined || value === null) return null
+ const stringValue = String(value).trim()
+ if (!stringValue || ['none', 'null', 'undefined'].includes(stringValue.toLowerCase())) return null
+ return stringValue
+}
+
+/**
+ * 获取媒体服务器条目的真实项目ID。
+ * @param target 媒体服务器跳转目标
+ */
+function getTargetItemId(target?: MediaServerLinkTarget): string | null {
+ return getValidLinkValue(target?.item_id ?? target?.itemId)
+}
+
+/**
+ * 获取媒体服务器条目的真实服务器ID。
+ * @param target 媒体服务器跳转目标
+ */
+function getTargetServerId(target?: MediaServerLinkTarget): string | null {
+ return getValidLinkValue(target?.server_id ?? target?.serverId)
+}
+
+/**
+ * 使用后端返回的真实ID修正Emby网页链接。
+ * @param playUrl 原始播放链接
+ * @param target 媒体服务器跳转目标
+ */
+function normalizeEmbyWebUrl(playUrl: string, target?: MediaServerLinkTarget): string {
+ try {
+ const url = new URL(playUrl)
+ const hash = url.hash || ''
+ const queryIndex = hash.indexOf('?')
+ if (queryIndex === -1) return playUrl
+
+ const hashPath = hash.slice(0, queryIndex)
+ const params = new URLSearchParams(hash.slice(queryIndex + 1))
+ const itemId = getTargetItemId(target)
+ const serverId = getTargetServerId(target)
+
+ if (itemId && (hashPath.includes('/item') || params.has('id'))) {
+ params.set('id', itemId)
+ }
+
+ if (serverId) {
+ params.set('serverId', serverId)
+ } else if (params.has('serverId') && !getValidLinkValue(params.get('serverId'))) {
+ params.delete('serverId')
+ }
+
+ url.hash = `${hashPath}?${params.toString()}`
+ return url.toString()
+ } catch (error) {
+ console.warn('修正Emby网页链接失败:', error)
+ return playUrl
+ }
+}
+
+/**
+ * 获取媒体服务器卡片可用的跳转链接。
+ * @param target 媒体服务器跳转目标
+ */
+function getMediaServerPlayUrl(target: MediaServerLinkTarget): string | null {
+ const playUrl = getValidLinkValue(target.link)
+ if (!playUrl) return null
+
+ const serverType = target.server_type?.toLowerCase()
+ if (serverType === 'emby' || serverType === 'zspace') {
+ return normalizeEmbyWebUrl(playUrl, target)
+ }
+ return playUrl
+}
+
+/**
+ * 打开媒体服务器卡片对应的播放页面。
+ * @param target 媒体服务器跳转目标
+ */
+export async function openMediaServerItem(target: MediaServerLinkTarget): Promise {
+ const playUrl = getMediaServerPlayUrl(target)
+ if (!playUrl) return
+ await openMediaServerWithAutoDetect(playUrl, undefined, target.server_type)
+}
+
/**
* 尝试跳转到APP,如果失败则跳转到网页
* @param appType APP类型
@@ -418,7 +516,7 @@ function buildEmbyDeepLink(playUrl: string): string {
// 提取serverId
const serverIdMatch = playUrl.match(/serverId=([^&]+)/)
if (serverIdMatch) {
- serverId = serverIdMatch[1]
+ serverId = getValidLinkValue(serverIdMatch[1])
}
}
@@ -427,7 +525,7 @@ function buildEmbyDeepLink(playUrl: string): string {
if (videosHashMatch) {
// 对于videos格式,我们使用parentId作为媒体ID
mediaId = videosHashMatch[2]
- serverId = videosHashMatch[1]
+ serverId = getValidLinkValue(videosHashMatch[1])
}
// 格式3: ?id=xxx (通用格式)