mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 15:36:49 +08:00
fix: correct media server card links
This commit is contained in:
@@ -1025,6 +1025,10 @@ export interface FileItem {
|
|||||||
export interface MediaServerPlayItem {
|
export interface MediaServerPlayItem {
|
||||||
// ID
|
// ID
|
||||||
id?: string | number
|
id?: string | number
|
||||||
|
// 媒体服务器项目ID
|
||||||
|
item_id?: string | number
|
||||||
|
// 媒体服务器ID
|
||||||
|
server_id?: string
|
||||||
// 标题
|
// 标题
|
||||||
title: string
|
title: string
|
||||||
// 副标题
|
// 副标题
|
||||||
@@ -1049,6 +1053,10 @@ export interface MediaServerLibrary {
|
|||||||
server: string
|
server: string
|
||||||
// ID
|
// ID
|
||||||
id?: string | number
|
id?: string | number
|
||||||
|
// 媒体服务器项目ID
|
||||||
|
item_id?: string | number
|
||||||
|
// 媒体服务器ID
|
||||||
|
server_id?: string
|
||||||
// 名称
|
// 名称
|
||||||
name: string
|
name: string
|
||||||
// 路径
|
// 路径
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { MediaServerPlayItem } from '@/api/types'
|
import type { MediaServerPlayItem } from '@/api/types'
|
||||||
import noImage from '@images/no-image.jpeg'
|
import noImage from '@images/no-image.jpeg'
|
||||||
import { openMediaServerWithAutoDetect } from '@/utils/appDeepLink'
|
import { openMediaServerItem } from '@/utils/appDeepLink'
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
media: Object as PropType<MediaServerPlayItem>,
|
media: Object as PropType<MediaServerPlayItem>,
|
||||||
@@ -25,8 +25,8 @@ function imageErrorHandler() {
|
|||||||
|
|
||||||
// 跳转播放
|
// 跳转播放
|
||||||
async function goPlay() {
|
async function goPlay() {
|
||||||
if (props.media?.link) {
|
if (props.media) {
|
||||||
await openMediaServerWithAutoDetect(props.media.link, undefined, props.media.server_type)
|
await openMediaServerItem(props.media)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import plex from '@images/misc/plex.png'
|
|||||||
import emby from '@images/misc/emby.png'
|
import emby from '@images/misc/emby.png'
|
||||||
import jellyfin from '@images/misc/jellyfin.png'
|
import jellyfin from '@images/misc/jellyfin.png'
|
||||||
import { getLogoUrl } from '@/utils/imageUtils'
|
import { getLogoUrl } from '@/utils/imageUtils'
|
||||||
import { openMediaServerWithAutoDetect } from '@/utils/appDeepLink'
|
import { openMediaServerItem } from '@/utils/appDeepLink'
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -49,8 +49,8 @@ function getDefaultImage() {
|
|||||||
|
|
||||||
// 跳转播放
|
// 跳转播放
|
||||||
async function goPlay() {
|
async function goPlay() {
|
||||||
if (props.media?.link) {
|
if (props.media) {
|
||||||
await openMediaServerWithAutoDetect(props.media.link, undefined, props.media.server_type)
|
await openMediaServerItem(props.media)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
import type { MediaServerPlayItem } from '@/api/types'
|
import type { MediaServerPlayItem } from '@/api/types'
|
||||||
import noImage from '@images/no-image.jpeg'
|
import noImage from '@images/no-image.jpeg'
|
||||||
import { openMediaServerWithAutoDetect } from '@/utils/appDeepLink'
|
import { openMediaServerItem } from '@/utils/appDeepLink'
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -38,8 +38,8 @@ const getImgUrl = computed(() => {
|
|||||||
|
|
||||||
// 跳转播放
|
// 跳转播放
|
||||||
async function goPlay(isHovering: boolean | null = false) {
|
async function goPlay(isHovering: boolean | null = false) {
|
||||||
if (props.media?.link && isHovering) {
|
if (props.media && isHovering) {
|
||||||
await openMediaServerWithAutoDetect(props.media.link, undefined, props.media.server_type)
|
await openMediaServerItem(props.media)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+100
-2
@@ -64,6 +64,104 @@ interface DoubanAppParams {
|
|||||||
fallbackUrl?: string
|
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<void> {
|
||||||
|
const playUrl = getMediaServerPlayUrl(target)
|
||||||
|
if (!playUrl) return
|
||||||
|
await openMediaServerWithAutoDetect(playUrl, undefined, target.server_type)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 尝试跳转到APP,如果失败则跳转到网页
|
* 尝试跳转到APP,如果失败则跳转到网页
|
||||||
* @param appType APP类型
|
* @param appType APP类型
|
||||||
@@ -418,7 +516,7 @@ function buildEmbyDeepLink(playUrl: string): string {
|
|||||||
// 提取serverId
|
// 提取serverId
|
||||||
const serverIdMatch = playUrl.match(/serverId=([^&]+)/)
|
const serverIdMatch = playUrl.match(/serverId=([^&]+)/)
|
||||||
if (serverIdMatch) {
|
if (serverIdMatch) {
|
||||||
serverId = serverIdMatch[1]
|
serverId = getValidLinkValue(serverIdMatch[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,7 +525,7 @@ function buildEmbyDeepLink(playUrl: string): string {
|
|||||||
if (videosHashMatch) {
|
if (videosHashMatch) {
|
||||||
// 对于videos格式,我们使用parentId作为媒体ID
|
// 对于videos格式,我们使用parentId作为媒体ID
|
||||||
mediaId = videosHashMatch[2]
|
mediaId = videosHashMatch[2]
|
||||||
serverId = videosHashMatch[1]
|
serverId = getValidLinkValue(videosHashMatch[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式3: ?id=xxx (通用格式)
|
// 格式3: ?id=xxx (通用格式)
|
||||||
|
|||||||
Reference in New Issue
Block a user