Merge branch 'main' of github.com:Jellify-Music/App

This commit is contained in:
Violet Caulfield
2025-07-22 10:17:26 -05:00
55 changed files with 1449 additions and 1268 deletions
+9 -13
View File
@@ -68,19 +68,15 @@ export function AlbumScreen({ route, navigation }: HomeAlbumProps): React.JSX.El
const allTracks = discs.flatMap((disc) => disc.data)
if (allTracks.length === 0) return
useLoadNewQueue.mutate(
{
track: allTracks[0],
index: 0,
tracklist: allTracks,
queue: album,
queuingType: QueuingType.FromSelection,
shuffled,
},
{
onSuccess: () => useStartPlayback.mutate(),
},
)
useLoadNewQueue({
track: allTracks[0],
index: 0,
tracklist: allTracks,
queue: album,
queuingType: QueuingType.FromSelection,
shuffled,
startPlayback: true,
})
}
return (
+9 -15
View File
@@ -15,7 +15,6 @@ import { StackParamList } from '../types'
import React from 'react'
import Icon from '../Global/components/icon'
import { useQueueContext } from '../../providers/Player/queue'
import { usePlayerContext } from '../../providers/Player'
import { QueuingType } from '../../enums/queuing-type'
import { fetchAlbumDiscs } from '../../api/queries/item'
@@ -26,7 +25,6 @@ export default function ArtistTabBar(
const { api } = useJellifyContext()
const { artist, scroll, albums } = useArtistContext()
const { useLoadNewQueue } = useQueueContext()
const { useStartPlayback } = usePlayerContext()
const { width } = useSafeAreaFrame()
@@ -47,19 +45,15 @@ export default function ArtistTabBar(
if (allTracks.length === 0) return
useLoadNewQueue.mutate(
{
track: allTracks[0],
index: 0,
tracklist: allTracks,
queue: artist,
queuingType: QueuingType.FromSelection,
shuffled,
},
{
onSuccess: () => useStartPlayback.mutate(),
},
)
useLoadNewQueue({
track: allTracks[0],
index: 0,
tracklist: allTracks,
queue: artist,
queuingType: QueuingType.FromSelection,
shuffled,
startPlayback: true,
})
} catch (error) {
console.error('Failed to play artist tracks:', error)
}
+1 -11
View File
@@ -5,9 +5,6 @@ import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { Text } from '../helpers/text'
import FastImage from 'react-native-fast-image'
import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
import { useQuery } from '@tanstack/react-query'
import { QueryKeys } from '../../../enums/query-keys'
import { fetchMediaInfo } from '../../../api/queries/media'
import { useJellifyContext } from '../../../providers'
interface CardProps extends TamaguiCardProps {
caption?: string | null | undefined
@@ -25,14 +22,7 @@ interface CardProps extends TamaguiCardProps {
* @param props
*/
export function ItemCard(props: CardProps) {
const { api, user } = useJellifyContext()
const mediaInfo = useQuery({
queryKey: [QueryKeys.MediaSources, props.item.Id!],
queryFn: () => fetchMediaInfo(api, user, props.item),
staleTime: Infinity,
enabled: props.item.Type === 'Audio',
})
const { api } = useJellifyContext()
return (
<View alignItems='center' margin={'$1.5'}>
+8 -13
View File
@@ -37,24 +37,19 @@ export default function ItemRow({
onPress?: () => void
circular?: boolean
}): React.JSX.Element {
const { useStartPlayback } = usePlayerContext()
const { useLoadNewQueue } = useQueueContext()
const gestureCallback = () => {
switch (item.Type) {
case 'Audio': {
useLoadNewQueue.mutate(
{
track: item,
tracklist: [item],
index: 0,
queue: 'Search',
queuingType: QueuingType.FromSelection,
},
{
onSuccess: () => useStartPlayback.mutate(),
},
)
useLoadNewQueue({
track: item,
tracklist: [item],
index: 0,
queue: 'Search',
queuingType: QueuingType.FromSelection,
startPlayback: true,
})
break
}
default: {
+8 -29
View File
@@ -16,7 +16,6 @@ import { networkStatusTypes } from '../../../components/Network/internetConnecti
import { useNetworkContext } from '../../../providers/Network'
import { useQuery } from '@tanstack/react-query'
import { QueryKeys } from '../../../enums/query-keys'
import { fetchMediaInfo } from '../../../api/queries/media'
import { useQueueContext } from '../../../providers/Player/queue'
import { fetchItem } from '../../../api/queries/item'
import { useJellifyContext } from '../../../providers'
@@ -67,22 +66,6 @@ export default function Track({
const isOffline = networkStatus === networkStatusTypes.DISCONNECTED
// Fetch media info so it's available in the player
const mediaInfo = useQuery({
queryKey: [QueryKeys.MediaSources, track.Id!],
queryFn: () => fetchMediaInfo(api, user, track),
staleTime: Infinity,
enabled: track.Type === 'Audio',
})
// Fetch album so it's available in the Details screen
const { data: album } = useQuery({
queryKey: [QueryKeys.Item, track.Id!], // Different key
queryFn: () => fetchItem(api, track.Id!),
staleTime: 60 * 60 * 1000 * 24, // 24 hours
enabled: !!track.Id, // Add proper enabled condition
})
return (
<Theme name={invertedColors ? 'inverted_purple' : undefined}>
<XStack
@@ -95,18 +78,14 @@ export default function Track({
if (onPress) {
onPress()
} else {
useLoadNewQueue.mutate(
{
track,
index,
tracklist: tracklist ?? playQueue.map((track) => track.item),
queue,
queuingType: QueuingType.FromSelection,
},
{
onSuccess: () => useStartPlayback.mutate(),
},
)
useLoadNewQueue({
track,
index,
tracklist: tracklist ?? playQueue.map((track) => track.item),
queue,
queuingType: QueuingType.FromSelection,
startPlayback: true,
})
}
}}
onLongPress={
@@ -8,7 +8,6 @@ import { QueuingType } from '../../../enums/queuing-type'
import { trigger } from 'react-native-haptic-feedback'
import Icon from '../../Global/components/icon'
import { useQueueContext } from '../../../providers/Player/queue'
import { usePlayerContext } from '../../../providers/Player'
import { H4 } from '../../../components/Global/helpers/text'
import { useDisplayContext } from '../../../providers/Display/display-provider'
export default function FrequentlyPlayedTracks({
@@ -23,7 +22,6 @@ export default function FrequentlyPlayedTracks({
isFetchingFrequentlyPlayed,
} = useHomeContext()
const { useStartPlayback } = usePlayerContext()
const { useLoadNewQueue } = useQueueContext()
const { horizontalItems } = useDisplayContext()
@@ -58,20 +56,16 @@ export default function FrequentlyPlayedTracks({
subCaption={`${track.Artists?.join(', ')}`}
squared
onPress={() => {
useLoadNewQueue.mutate(
{
useLoadNewQueue({
track,
index,
tracklist: frequentlyPlayed?.pages.flatMap((page) => page) ?? [
track,
index,
tracklist: frequentlyPlayed?.pages.flatMap((page) => page) ?? [
track,
],
queue: 'On Repeat',
queuingType: QueuingType.FromSelection,
},
{
onSuccess: () => useStartPlayback.mutate(),
},
)
],
queue: 'On Repeat',
queuingType: QueuingType.FromSelection,
startPlayback: true,
})
}}
onLongPress={() => {
trigger('impactMedium')
+11 -15
View File
@@ -18,7 +18,7 @@ export default function RecentlyPlayed({
}: {
navigation: NativeStackNavigationProp<StackParamList>
}): React.JSX.Element {
const { nowPlaying, useStartPlayback } = usePlayerContext()
const { nowPlaying } = usePlayerContext()
const { useLoadNewQueue } = useQueueContext()
@@ -59,20 +59,16 @@ export default function RecentlyPlayed({
testId={`recently-played-${index}`}
item={recentlyPlayedTrack}
onPress={() => {
useLoadNewQueue.mutate(
{
track: recentlyPlayedTrack,
index: index,
tracklist: recentTracks?.pages.flatMap((page) => page) ?? [
recentlyPlayedTrack,
],
queue: 'Recently Played',
queuingType: QueuingType.FromSelection,
},
{
onSuccess: () => useStartPlayback.mutate(),
},
)
useLoadNewQueue({
track: recentlyPlayedTrack,
index: index,
tracklist: recentTracks?.pages.flatMap((page) => page) ?? [
recentlyPlayedTrack,
],
queue: 'Recently Played',
queuingType: QueuingType.FromSelection,
startPlayback: true,
})
}}
onLongPress={() => {
trigger('impactMedium')
+7 -10
View File
@@ -21,7 +21,7 @@ export default function Scrubber(): React.JSX.Element {
const { width } = useSafeAreaFrame()
// Get progress from the track player with the specified update interval
const progress = useProgress(UPDATE_INTERVAL, false)
const { position, duration } = useProgress(UPDATE_INTERVAL)
// Single source of truth for the current position
const [displayPosition, setDisplayPosition] = useState<number>(0)
@@ -33,16 +33,13 @@ export default function Scrubber(): React.JSX.Element {
// Calculate maximum track duration in slider units
const maxDuration = useMemo(() => {
return progress?.duration
? Math.round(progress.duration * ProgressMultiplier)
: ProgressMultiplier
}, [progress?.duration])
return Math.round(duration * ProgressMultiplier)
}, [duration])
// Calculate current position in slider units
const calculatedPosition = useMemo(() => {
if (!progress?.position) return 0
return Math.round(progress.position * ProgressMultiplier)
}, [progress?.position])
return Math.round(position * ProgressMultiplier)
}, [position])
// Update display position from playback progress
useEffect(() => {
@@ -93,8 +90,8 @@ export default function Scrubber(): React.JSX.Element {
// Get total duration in seconds
const totalSeconds = useMemo(() => {
return progress?.duration ? Math.round(progress.duration) : 0
}, [progress?.duration])
return Math.round(duration)
}, [duration])
return (
<GestureDetector gesture={scrubGesture}>
+5 -2
View File
@@ -10,6 +10,7 @@ import Animated from 'react-native-reanimated'
import { Gesture } from 'react-native-gesture-handler'
import { useState } from 'react'
import { trigger } from 'react-native-haptic-feedback'
import { isUndefined } from 'lodash'
const gesture = Gesture.Pan().runOnJS(true)
@@ -95,7 +96,8 @@ export default function Queue({
showArtwork
testID={`queue-item-${getIndex()}`}
onPress={() => {
useSkip.mutate(getIndex())
const index = getIndex()
if (!isUndefined(index)) useSkip.mutate(index)
}}
onLongPress={() => {
trigger('impactLight')
@@ -104,7 +106,8 @@ export default function Queue({
isNested
showRemove
onRemove={() => {
if (getIndex()) useRemoveFromQueue.mutate(getIndex()!)
const index = getIndex()
if (!isUndefined(index)) useRemoveFromQueue.mutate(index)
}}
/>
</XStack>
+9 -15
View File
@@ -17,7 +17,6 @@ import { useSettingsContext } from '../../../../src/providers/Settings'
import { ActivityIndicator } from 'react-native'
import { mapDtoToTrack } from '../../../utils/mappings'
import { useQueueContext } from '../../../providers/Player/queue'
import { usePlayerContext } from '../../../providers/Player'
import { QueuingType } from '../../../enums/queuing-type'
export default function PlayliistTracklistHeader(
@@ -150,7 +149,6 @@ function PlaylistHeaderControls({
const { useDownloadMultiple, pendingDownloads } = useNetworkContext()
const { downloadQuality, streamingQuality } = useSettingsContext()
const { useLoadNewQueue } = useQueueContext()
const { useStartPlayback } = usePlayerContext()
const isDownloading = pendingDownloads.length != 0
const { sessionId, api } = useJellifyContext()
@@ -165,19 +163,15 @@ function PlaylistHeaderControls({
const playPlaylist = (shuffled: boolean = false) => {
if (!playlistTracks || playlistTracks.length === 0) return
useLoadNewQueue.mutate(
{
track: playlistTracks[0],
index: 0,
tracklist: playlistTracks,
queue: playlist,
queuingType: QueuingType.FromSelection,
shuffled,
},
{
onSuccess: () => useStartPlayback.mutate(),
},
)
useLoadNewQueue({
track: playlistTracks[0],
index: 0,
tracklist: playlistTracks,
queue: playlist,
queuingType: QueuingType.FromSelection,
shuffled,
startPlayback: true,
})
}
return (
@@ -38,7 +38,7 @@ export default function InfoTabIndex({ navigation }: InfoTabNativeStackNavigatio
title: `Jellify`,
subTitle: version,
iconName: 'jellyfish',
iconColor: '$borderColor',
iconColor: '$secondary',
children: (
<YStack gap={'$2'}>
<Text
@@ -14,22 +14,11 @@ export default function PlaybackTab(): React.JSX.Element {
return (
<SettingsListGroup
settingsList={[
{
title: 'Gapless Playback',
subTitle: 'Seamless transitions between tracks',
iconName: 'skip-next',
iconColor: '$borderColor',
children: (
<Text fontSize='$3' color='$color10' padding='$3'>
Gapless playback is automatically enabled for smooth music transitions.
</Text>
),
},
{
title: 'Streaming Quality',
subTitle: `Current: ${getQualityLabel(streamingQuality)}${getBandwidthEstimate(streamingQuality)}`,
iconName: 'wifi',
iconColor: '$primary',
iconName: 'sine-wave',
iconColor: getStreamingQualityIconColor(streamingQuality),
children: (
<YStack gap='$2' paddingVertical='$2'>
<Text bold fontSize='$4'>
@@ -72,3 +61,18 @@ export default function PlaybackTab(): React.JSX.Element {
/>
)
}
function getStreamingQualityIconColor(streamingQuality: StreamingQuality): string {
switch (streamingQuality) {
case 'original':
return '$success'
case 'high':
return '$success'
case 'medium':
return '$secondary'
case 'low':
return '$danger'
default:
return '$borderColor'
}
}
View File
+3 -20
View File
@@ -50,7 +50,7 @@ interface PlayerContext {
shuffled: boolean
useToggleRepeatMode: UseMutationResult<void, Error, void, unknown>
useToggleShuffle: UseMutationResult<void, Error, void, unknown>
useStartPlayback: UseMutationResult<void, Error, void, unknown>
useStartPlayback: () => void
useTogglePlayback: UseMutationResult<void, Error, void, unknown>
useSeekTo: UseMutationResult<void, Error, number, unknown>
useSeekBy: UseMutationResult<void, Error, number, unknown>
@@ -358,7 +358,7 @@ const PlayerContextInitializer = () => {
/**
* A mutation to handle starting playback
*/
const useStartPlayback = useMutation({
const { mutate: useStartPlayback } = useMutation({
mutationFn: TrackPlayer.play,
})
@@ -661,24 +661,7 @@ export const PlayerContext = createContext<PlayerContext>({
submittedAt: 0,
},
playbackState: undefined,
useStartPlayback: {
mutate: () => {},
mutateAsync: async () => {},
data: undefined,
error: null,
variables: undefined,
isError: false,
isIdle: true,
isPaused: false,
isPending: false,
isSuccess: false,
status: 'idle',
reset: () => {},
context: {},
failureCount: 0,
failureReason: null,
submittedAt: 0,
},
useStartPlayback: () => {},
useTogglePlayback: {
mutate: () => {},
mutateAsync: async () => {},
@@ -1,7 +1,6 @@
import JellifyTrack from '../types/JellifyTrack'
import { QueuingType } from '../enums/queuing-type'
import { QueuingType } from '../../enums/queuing-type'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { Queue } from './types/queue-item'
import { Queue } from '../../player/types/queue-item'
/**
* A mutation to handle loading a new queue.
@@ -34,6 +33,11 @@ export interface QueueMutation {
* Whether the queue should be shuffled.
*/
shuffled?: boolean | undefined
/**
* Whether to start playback immediately.
*/
startPlayback?: boolean | undefined
}
/**
+12 -34
View File
@@ -3,7 +3,7 @@ import { createContext } from 'react'
import { Queue } from '../../player/types/queue-item'
import { Section } from '../../components/Player/types'
import { useMutation, UseMutationResult } from '@tanstack/react-query'
import { AddToQueueMutation, QueueMutation, QueueOrderMutation } from '../../player/interfaces'
import { AddToQueueMutation, QueueMutation, QueueOrderMutation } from './interfaces'
import { storage } from '../../constants/storage'
import { MMKVStorageKeys } from '../../enums/mmkv-storage-keys'
import JellifyTrack from '../../types/JellifyTrack'
@@ -14,7 +14,6 @@ import { useSettingsContext } from '../Settings'
import { QueuingType } from '../../enums/queuing-type'
import TrackPlayer, { Event, useTrackPlayerEvents } from 'react-native-track-player'
import { findPlayQueueIndexStart } from './utils'
import { play, seekTo } from 'react-native-track-player/lib/src/trackPlayer'
import { trigger } from 'react-native-haptic-feedback'
import { usePerformanceMonitor } from '../../hooks/use-performance-monitor'
@@ -27,6 +26,7 @@ import Toast from 'react-native-toast-message'
import { useJellifyContext } from '..'
import { networkStatusTypes } from '@/src/components/Network/internetConnectionWatcher'
import move from './utils/move'
import { ensureUpcomingTracksInQueue } from '../../player/helpers/gapless'
/**
* @description The context for managing the queue
@@ -76,7 +76,7 @@ interface QueueContext {
/**
* A hook that loads a new queue of tracks
*/
useLoadNewQueue: UseMutationResult<void, Error, QueueMutation, unknown>
useLoadNewQueue: (mutation: QueueMutation) => void
/**
* A hook that removes upcoming tracks from the queue
@@ -192,17 +192,10 @@ const QueueContextInitailizer = () => {
if (itemIndex !== -1) {
newIndex = itemIndex
console.debug(`Active track changed to index ${itemIndex}`)
console.debug(`Active track changed to item at index: ${itemIndex}`)
// Ensure upcoming tracks are in correct order (important for shuffle)
// try {
// const { ensureUpcomingTracksInQueue } = await import(
// '../../player/helpers/gapless'
// )
// await ensureUpcomingTracksInQueue(playQueue, index)
// } catch (error) {
// console.debug('Failed to ensure upcoming tracks on track change:', error)
// }
await ensureUpcomingTracksInQueue(playQueue, itemIndex)
} else {
console.warn('No index found for active track')
}
@@ -356,7 +349,7 @@ const QueueContextInitailizer = () => {
`Queued ${queue.length} tracks, starting at ${finalStartIndex}${shuffleQueue ? ' (shuffled)' : ''}`,
)
await play()
await TrackPlayer.play()
}
/**
@@ -450,7 +443,7 @@ const QueueContextInitailizer = () => {
if (currentIndex > 0 && Math.floor(position) < SKIP_TO_PREVIOUS_THRESHOLD) {
TrackPlayer.skipToPrevious()
} else await seekTo(0)
} else await TrackPlayer.seekTo(0)
}
const skip = async (index?: number | undefined) => {
@@ -530,7 +523,7 @@ const QueueContextInitailizer = () => {
},
})
const useLoadNewQueue = useMutation({
const { mutate: useLoadNewQueue } = useMutation({
mutationFn: async ({
index,
track,
@@ -539,9 +532,11 @@ const QueueContextInitailizer = () => {
queue,
shuffled,
}: QueueMutation) => loadQueue(tracklist, queue, index, shuffled),
onSuccess: async (data, { queue }: QueueMutation) => {
onSuccess: async (data, { queue, startPlayback }: QueueMutation) => {
trigger('notificationSuccess')
startPlayback && (await TrackPlayer.play())
if (typeof queue === 'object' && api && user) await markItemPlayed(api, user, queue)
},
})
@@ -734,24 +729,7 @@ export const QueueContext = createContext<QueueContext>({
failureReason: null,
submittedAt: 0,
},
useLoadNewQueue: {
mutate: () => {},
mutateAsync: async () => {},
data: undefined,
error: null,
variables: undefined,
isError: false,
isIdle: true,
isPaused: false,
isPending: false,
isSuccess: false,
status: 'idle',
reset: () => {},
context: {},
failureCount: 0,
failureReason: null,
submittedAt: 0,
},
useLoadNewQueue: () => {},
useSkip: {
mutate: () => {},
mutateAsync: async () => {},
+20 -11
View File
@@ -1,17 +1,22 @@
import { isEmpty } from 'lodash'
import { isEmpty, isUndefined } from 'lodash'
import { QueuingType } from '../../../enums/queuing-type'
import JellifyTrack from '../../../types/JellifyTrack'
import { getActiveTrackIndex } from 'react-native-track-player/lib/src/trackPlayer'
import TrackPlayer from 'react-native-track-player'
/**
* Finds and returns the index of the player queue to insert additional tracks into
* @param playQueue The current player queue
* @returns The index to insert songs to play next at
*/
export const findPlayNextIndexStart = async (playQueue: JellifyTrack[]) => {
export async function findPlayNextIndexStart(playQueue: JellifyTrack[]) {
if (isEmpty(playQueue)) return 0
return (await getActiveTrackIndex())! + 1
const activeTrack = (await TrackPlayer.getActiveTrack()) as JellifyTrack
const activeIndex = playQueue.findIndex((track) => track.item.Id === activeTrack?.item.Id)
if (isUndefined(activeTrack) || activeIndex === -1) return 0
else return activeIndex + 1
}
/**
@@ -19,16 +24,20 @@ export const findPlayNextIndexStart = async (playQueue: JellifyTrack[]) => {
* @param playQueue The current player queue
* @returns The index to insert songs to add to the user queue
*/
export const findPlayQueueIndexStart = async (playQueue: JellifyTrack[]) => {
export async function findPlayQueueIndexStart(playQueue: JellifyTrack[]) {
if (isEmpty(playQueue)) return 0
const activeIndex = await getActiveTrackIndex()
const activeTrack = (await TrackPlayer.getActiveTrack()) as JellifyTrack
if (playQueue.findIndex((track) => track.QueuingType === QueuingType.FromSelection) === -1)
return activeIndex! + 1
const activeIndex = playQueue.findIndex((track) => track.item.Id === activeTrack?.item.Id)
return playQueue.findIndex(
(queuedTrack, index) =>
queuedTrack.QueuingType === QueuingType.FromSelection && index > activeIndex!,
if (isUndefined(activeTrack) || activeIndex === -1) return 0
const insertIndex = playQueue.findIndex(
({ QueuingType: queuingType, index }) =>
queuingType === QueuingType.FromSelection && index > activeIndex,
)
if (insertIndex === -1) return playQueue.length
else return insertIndex
}
+7 -1
View File
@@ -79,6 +79,7 @@ export function Tabs({
name='music-box-multiple'
color={color}
size={size}
testID='library-tab-icon'
/>
),
}}
@@ -106,7 +107,12 @@ export function Tabs({
options={{
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name='earth' color={color} size={size} />
<MaterialCommunityIcons
name='earth'
color={color}
size={size}
testID='discover-tab-icon'
/>
),
}}
/>
+22 -15
View File
@@ -4,16 +4,16 @@ import {
PlaybackInfoResponse,
} from '@jellyfin/sdk/lib/generated-client/models'
import JellifyTrack from '../types/JellifyTrack'
import { RatingType, TrackType } from 'react-native-track-player'
import { TrackType } from 'react-native-track-player'
import { QueuingType } from '../enums/queuing-type'
import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
import { isUndefined } from 'lodash'
import { JellifyDownload } from '../types/JellifyDownload'
import { queryClient } from '../constants/query-client'
import { QueryKeys } from '../enums/query-keys'
import { Api } from '@jellyfin/sdk/lib/api'
import RNFS from 'react-native-fs'
import { DownloadQuality, StreamingQuality } from '../providers/Settings'
import { Platform } from 'react-native'
/**
* The container that the Jellyfin server will attempt to transcode to
@@ -25,6 +25,16 @@ import { DownloadQuality, StreamingQuality } from '../providers/Settings'
*/
const transcodingContainer = 'ts'
/**
* The type of track to use for the player
*
* iOS can use HLS, Android can't - and therefore uses Default
*
* Why? I'm not sure - someone way smarter than me can probably explain it
* - Violet Caulfield - 2025-07-20
*/
const type = Platform.OS === 'ios' ? TrackType.HLS : TrackType.Default
/**
* Gets quality-specific parameters for transcoding
*
@@ -97,15 +107,16 @@ export function mapDtoToTrack(
console.debug(
`Mapping BaseItemDTO to Track object with streaming quality: ${qualityForStreaming}`,
)
const isFavorite = !isUndefined(item.UserData) && (item.UserData.IsFavorite ?? false)
const downloads = downloadedTracks.filter((download) => download.item.Id === item.Id)
let url: string
let image: string | undefined
if (downloads.length > 0 && downloads[0].path)
if (downloads.length > 0 && downloads[0].path) {
url = `file://${RNFS.DocumentDirectoryPath}/${downloads[0].path.split('/').pop()}`
else {
image = `file://${RNFS.DocumentDirectoryPath}/${downloads[0].artwork?.split('/').pop()}`
} else {
const PlaybackInfoResponse = queryClient.getQueryData([
QueryKeys.MediaSources,
item.Id!,
@@ -118,12 +129,15 @@ export function mapDtoToTrack(
)
url = PlaybackInfoResponse.MediaSources![0].TranscodingUrl
else url = `${api.basePath}/Audio/${item.Id!}/universal?${new URLSearchParams(urlParams)}`
image = item.AlbumId
? getImageApi(api).getItemImageUrlById(item.AlbumId, ImageType.Primary)
: undefined
}
console.debug(url.length)
return {
url,
type: TrackType.Default,
type,
headers: {
'X-Emby-Token': api.accessToken,
},
@@ -131,14 +145,7 @@ export function mapDtoToTrack(
album: item.Album,
artist: item.Artists?.join(', '),
duration: item.RunTimeTicks,
artwork: item.AlbumId
? getImageApi(api).getItemImageUrlById(item.AlbumId, ImageType.Primary, {
width: 300,
height: 300,
})
: undefined,
rating: isFavorite ? RatingType.Heart : undefined,
artwork: image,
item,
QueuingType: queuingType ?? QueuingType.DirectlyQueued,
} as JellifyTrack