mirror of
https://github.com/anultravioletaurora/Jellify.git
synced 2025-12-19 04:19:44 -06:00
Artwork Retrieval Fixes and Network Timeout Improvements (#760)
* fix: increase default timeout for nitroFetch and Axios instance to 60 seconds * feat: prioritize track artwork over album artwork in image URL retrieval * fix: enhance image retrieval logic to prioritize album artist's image if album artwork is unavailable
This commit is contained in:
@@ -20,7 +20,7 @@ export function getItemImageUrl(
|
||||
type: ImageType,
|
||||
options?: ImageUrlOptions,
|
||||
): string | undefined {
|
||||
const { AlbumId, AlbumPrimaryImageTag, ImageTags, Id } = item
|
||||
const { AlbumId, AlbumPrimaryImageTag, ImageTags, Id, AlbumArtists } = item
|
||||
|
||||
if (!api) return undefined
|
||||
|
||||
@@ -32,15 +32,33 @@ export function getItemImageUrl(
|
||||
quality: options?.quality ?? 90,
|
||||
}
|
||||
|
||||
return AlbumId
|
||||
? getImageApi(api).getItemImageUrlById(AlbumId, type, {
|
||||
...imageParams,
|
||||
tag: AlbumPrimaryImageTag ?? undefined,
|
||||
})
|
||||
: Id
|
||||
? getImageApi(api).getItemImageUrlById(Id, type, {
|
||||
...imageParams,
|
||||
tag: ImageTags ? ImageTags[type] : undefined,
|
||||
})
|
||||
: undefined
|
||||
// Check if the item has its own image for the requested type first
|
||||
const hasOwnImage = ImageTags && ImageTags[type]
|
||||
|
||||
if (hasOwnImage && Id) {
|
||||
// Use the item's own image (e.g., track-specific artwork)
|
||||
return getImageApi(api).getItemImageUrlById(Id, type, {
|
||||
...imageParams,
|
||||
tag: ImageTags[type],
|
||||
})
|
||||
} else if (AlbumId && AlbumPrimaryImageTag) {
|
||||
// Fall back to album image (only if the album has an image)
|
||||
return getImageApi(api).getItemImageUrlById(AlbumId, type, {
|
||||
...imageParams,
|
||||
tag: AlbumPrimaryImageTag,
|
||||
})
|
||||
} else if (AlbumArtists && AlbumArtists.length > 0 && AlbumArtists[0].Id) {
|
||||
// Fall back to first album artist's image
|
||||
return getImageApi(api).getItemImageUrlById(AlbumArtists[0].Id, type, {
|
||||
...imageParams,
|
||||
})
|
||||
} else if (Id) {
|
||||
// Last resort: use item's own ID
|
||||
return getImageApi(api).getItemImageUrlById(Id, type, {
|
||||
...imageParams,
|
||||
tag: ImageTags ? ImageTags[type] : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export async function nitroFetch<T>(
|
||||
api: Api | undefined,
|
||||
path: string,
|
||||
params?: Record<string, string | number | boolean | undefined | string[]>,
|
||||
timeoutMs: number = 30000,
|
||||
timeoutMs: number = 60000,
|
||||
): Promise<T> {
|
||||
if (isUndefined(api)) {
|
||||
throw new Error('Client instance not set')
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react'
|
||||
import Input from '../Global/helpers/input'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import ItemRow from '../Global/components/item-row'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
@@ -108,9 +109,37 @@ export default function Search({
|
||||
}
|
||||
ItemSeparatorComponent={() => <Separator />}
|
||||
ListEmptyComponent={() => {
|
||||
// Show spinner while fetching
|
||||
if (fetchingResults) {
|
||||
return (
|
||||
<YStack alignContent='center' justifyContent='center' marginTop={'$4'}>
|
||||
<Spinner />
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
// Show "No Results" when user has searched but got no results
|
||||
if (!isEmpty(searchString) && isEmpty(items)) {
|
||||
return (
|
||||
<YStack
|
||||
alignItems='center'
|
||||
justifyContent='center'
|
||||
marginTop={'$8'}
|
||||
gap={'$3'}
|
||||
paddingHorizontal={'$4'}
|
||||
>
|
||||
<H3>No Results</H3>
|
||||
<Text textAlign='center'>
|
||||
{`No results found for "${searchString}". Try a different search term.`}
|
||||
</Text>
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
// Show suggestions when no search is active
|
||||
return (
|
||||
<YStack alignContent='center' justifyContent='flex-end' marginTop={'$4'}>
|
||||
{fetchingResults ? <Spinner /> : <Suggestions suggestions={suggestions} />}
|
||||
<Suggestions suggestions={suggestions} />
|
||||
</YStack>
|
||||
)
|
||||
}}
|
||||
|
||||
@@ -3,10 +3,10 @@ import axios from 'axios'
|
||||
/**
|
||||
* The Axios instance for making HTTP requests.
|
||||
*
|
||||
* Default timeout is set to 15 seconds.
|
||||
* Default timeout is set to 60 seconds.
|
||||
*/
|
||||
const AXIOS_INSTANCE = axios.create({
|
||||
timeout: 15 * 1000, // 15 seconds
|
||||
timeout: 60000,
|
||||
})
|
||||
|
||||
export default AXIOS_INSTANCE
|
||||
|
||||
@@ -23,6 +23,35 @@ import StreamingQuality from '../enums/audio-quality'
|
||||
import { getAudioCache } from '../api/mutations/download/offlineModeUtils'
|
||||
import RNFS from 'react-native-fs'
|
||||
|
||||
/**
|
||||
* Gets the artwork URL for a track, prioritizing the track's own artwork over the album's artwork.
|
||||
* Falls back to artist image if no album artwork is available.
|
||||
*
|
||||
* @param api The API instance
|
||||
* @param item The track item
|
||||
* @returns The artwork URL or undefined
|
||||
*/
|
||||
function getTrackArtworkUrl(api: Api, item: BaseItemDto): string | undefined {
|
||||
const { AlbumId, AlbumPrimaryImageTag, ImageTags, Id, AlbumArtists } = item
|
||||
|
||||
// Check if the track has its own Primary image
|
||||
if (ImageTags?.Primary && Id) {
|
||||
return getImageApi(api).getItemImageUrlById(Id, ImageType.Primary)
|
||||
}
|
||||
|
||||
// Fall back to album artwork (only if the album has an image)
|
||||
if (AlbumId && AlbumPrimaryImageTag) {
|
||||
return getImageApi(api).getItemImageUrlById(AlbumId, ImageType.Primary)
|
||||
}
|
||||
|
||||
// Fall back to first album artist's image
|
||||
if (AlbumArtists && AlbumArtists.length > 0 && AlbumArtists[0].Id) {
|
||||
return getImageApi(api).getItemImageUrlById(AlbumArtists[0].Id, ImageType.Primary)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets quality-specific parameters for transcoding
|
||||
*
|
||||
@@ -108,9 +137,7 @@ export function mapDtoToTrack(
|
||||
} else
|
||||
trackMediaInfo = {
|
||||
url: buildAudioApiUrl(api, item, deviceProfile),
|
||||
image: item.AlbumId
|
||||
? getImageApi(api).getItemImageUrlById(item.AlbumId, ImageType.Primary)
|
||||
: undefined,
|
||||
image: getTrackArtworkUrl(api, item),
|
||||
duration: convertRunTimeTicksToSeconds(item.RunTimeTicks!),
|
||||
item,
|
||||
sessionId: mediaInfo?.PlaySessionId,
|
||||
@@ -162,14 +189,12 @@ function buildTranscodedTrack(
|
||||
mediaSourceInfo: MediaSourceInfo,
|
||||
sessionId: string | null | undefined,
|
||||
): TrackMediaInfo {
|
||||
const { AlbumId, RunTimeTicks } = item
|
||||
const { RunTimeTicks } = item
|
||||
|
||||
return {
|
||||
type: TrackType.HLS,
|
||||
url: `${api.basePath}${mediaSourceInfo.TranscodingUrl}`,
|
||||
image: AlbumId
|
||||
? getImageApi(api).getItemImageUrlById(AlbumId, ImageType.Primary)
|
||||
: undefined,
|
||||
image: getTrackArtworkUrl(api, item),
|
||||
duration: convertRunTimeTicksToSeconds(RunTimeTicks ?? 0),
|
||||
mediaSourceInfo,
|
||||
item,
|
||||
|
||||
Reference in New Issue
Block a user