bump nitro player - incorporate extra params

This commit is contained in:
Violet Caulfield
2026-02-05 23:54:13 -06:00
parent 62f57022d9
commit ea84b93bf9
15 changed files with 282 additions and 123 deletions

View File

@@ -43,7 +43,7 @@
"react-native-nitro-fetch": "0.1.7",
"react-native-nitro-modules": "0.33.2",
"react-native-nitro-ota": "^0.10.0",
"react-native-nitro-player": "0.3.0-alpha.14",
"react-native-nitro-player": "0.3.0-alpha.16",
"react-native-pager-view": "8.0.0",
"react-native-reanimated": "4.1.6",
"react-native-safe-area-context": "5.6.2",
@@ -1920,7 +1920,7 @@
"react-native-nitro-ota": ["react-native-nitro-ota@0.10.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "0.32.0" } }, "sha512-pxmdaeNdUdnYdD1M8BpbtQo4mZrtljWFg0gspuIohTJqi97JYIRq0b+SReN0sMMo0w912k4XXSGMr/IduGoMNg=="],
"react-native-nitro-player": ["react-native-nitro-player@0.3.0-alpha.14", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "*" } }, "sha512-nR1RPWr7dh4htLa5nyWLGxavoo7f3HmVfFzsLwiCeE3g8belZoY5HHMJVu72eb04oIHHDomcit99WiBQfOOQ5w=="],
"react-native-nitro-player": ["react-native-nitro-player@0.3.0-alpha.16", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "*" } }, "sha512-fpkJAuxrGuaZa/xjnzmDvrcxSCASP7AfQZ8nfZkFzIq1P/QqPaiSSsD/SWrRglu/jd+LrX2vWhjq3qzxTSk6sQ=="],
"react-native-pager-view": ["react-native-pager-view@8.0.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-oAwlWT1lhTkIs9HhODnjNNl/owxzn9DP1MbP+az6OTUdgbmzA16Up83sBH8NRKwrH8rNm7iuWnX1qMqiiWOLhg=="],

View File

@@ -139,7 +139,7 @@ PODS:
- SSZipArchive
- Yoga
- NitroOtaBundleManager (0.10.0)
- NitroPlayer (0.3.0-alpha.14):
- NitroPlayer (0.3.0-alpha.16):
- boost
- DoubleConversion
- fast_float
@@ -3655,7 +3655,7 @@ SPEC CHECKSUMS:
NitroModules: 11bba9d065af151eae51e38a6425e04c3b223ff3
NitroOta: 92d4eb528566b6babf5e4a30adbda44bfa803a9b
NitroOtaBundleManager: 8fad871db2daf6b9ee6f04a100c79605cfa81e8d
NitroPlayer: 8bc7be5caa2240ed636e4c1128791473eaf07a8b
NitroPlayer: 0dd9fb5af8b18fb603a1db487fca3eb9326be47b
NitroSuperconfig: 54d86ee90bb78cbca09d119ea775a53ffbedb0fc
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669

View File

@@ -75,7 +75,7 @@
"react-native-nitro-fetch": "0.1.7",
"react-native-nitro-modules": "0.33.2",
"react-native-nitro-ota": "^0.10.0",
"react-native-nitro-player": "0.3.0-alpha.14",
"react-native-nitro-player": "0.3.0-alpha.16",
"react-native-pager-view": "8.0.0",
"react-native-reanimated": "4.1.6",
"react-native-safe-area-context": "5.6.2",

View File

@@ -1,13 +0,0 @@
diff --git a/node_modules/react-native-nitro-player/ios/core/TrackPlayerCore.swift b/node_modules/react-native-nitro-player/ios/core/TrackPlayerCore.swift
index 7d57df3..3b684ab 100644
--- a/node_modules/react-native-nitro-player/ios/core/TrackPlayerCore.swift
+++ b/node_modules/react-native-nitro-player/ios/core/TrackPlayerCore.swift
@@ -630,8 +630,6 @@ class TrackPlayerCore: NSObject {
self.updatePlayerQueue(tracks: playlist.tracks)
// Emit initial state (paused/stopped before play)
self.emitStateChange()
- // Automatically start playback after loading
- self.play()
} else {
print(" ❌ Playlist NOT FOUND")
print(String(repeating: "🎼", count: Constants.playlistSeparatorLength) + "\n")

View File

@@ -2,7 +2,7 @@ import { useQuery, UseQueryResult } from '@tanstack/react-query'
import LyricsQueryKey from './keys'
import { isUndefined } from 'lodash'
import { fetchRawLyrics } from './utils'
import { useApi } from '../../../stores'
import { getApi, useApi } from '../../../stores'
import { usePlayerQueueStore } from '../../../stores/player/queue'
import { useNowPlaying } from 'react-native-nitro-player'
import JellifyTrack from '../../../types/JellifyTrack'
@@ -13,19 +13,13 @@ import JellifyTrack from '../../../types/JellifyTrack'
* @returns a {@link UseQueryResult} for the
*/
const useRawLyrics = () => {
const api = useApi()
const playerState = useNowPlaying()
const currentTrack = playerState.currentTrack
const queue = usePlayerQueueStore((state) => state.queue)
// Find the full JellifyTrack in the queue by ID
const nowPlaying = currentTrack
? ((queue.find((t) => t.id === currentTrack.id) as JellifyTrack | undefined) ?? undefined)
: undefined
const api = getApi()
const { currentTrack } = useNowPlaying()
return useQuery({
queryKey: LyricsQueryKey(nowPlaying),
queryFn: () => fetchRawLyrics(api, nowPlaying!.item.Id!),
enabled: !isUndefined(nowPlaying),
queryKey: LyricsQueryKey(currentTrack),
queryFn: () => fetchRawLyrics(api, currentTrack!.id!),
enabled: !isUndefined(currentTrack),
staleTime: (data) => (!isUndefined(data) ? Infinity : 0),
})
}

View File

@@ -1,5 +1,6 @@
import JellifyTrack from '@/src/types/JellifyTrack'
import { TrackItem } from 'react-native-nitro-player'
const LyricsQueryKey = (track: JellifyTrack | undefined) => ['TRACK_LYRICS', track?.item.Id]
const LyricsQueryKey = (track: TrackItem | null) => ['TRACK_LYRICS', track?.id]
export default LyricsQueryKey

View File

@@ -10,9 +10,7 @@ import { useEffect } from 'react'
import usePlayerEngineStore from '../../../stores/player/engine'
import useRawLyrics from '../../../api/queries/lyrics'
import Animated, { Easing, FadeIn, FadeOut } from 'react-native-reanimated'
import { usePlayerQueueStore } from '../../../stores/player/queue'
import { useNowPlaying } from 'react-native-nitro-player'
import JellifyTrack from '../../../types/JellifyTrack'
import { useCurrentTrack } from '../../../stores/player/queue'
export default function Footer(): React.JSX.Element {
const navigation = useNavigation<NativeStackNavigationProp<PlayerParamList>>()
@@ -21,13 +19,7 @@ export default function Footer(): React.JSX.Element {
const remoteMediaClient = useRemoteMediaClient()
const playerState = useNowPlaying()
const currentTrack = playerState.currentTrack
const queue = usePlayerQueueStore((state) => state.queue)
// Find the full JellifyTrack in the queue by ID
const nowPlaying = currentTrack
? ((queue.find((t) => t.id === currentTrack.id) as JellifyTrack | undefined) ?? undefined)
: undefined
const nowPlaying = useCurrentTrack()
const { data: lyrics } = useRawLyrics()
@@ -85,7 +77,6 @@ export default function Footer(): React.JSX.Element {
title: nowPlaying?.title,
artist: nowPlaying?.artist,
albumTitle: nowPlaying?.album || '',
releaseDate: nowPlaying?.date || '',
images: [{ url: nowPlaying?.artwork || '' }],
},
},

View File

@@ -88,7 +88,10 @@ function PlayerArtwork(): React.JSX.Element {
{nowPlaying && (
<Animated.View key={`${nowPlaying.id}-item-image`} style={animatedStyle}>
<ItemImage
item={nowPlaying.item}
item={{
Name: nowPlaying!.title,
Id: nowPlaying!.id,
}}
testID='player-image-test-id'
imageOptions={{ maxWidth: 800, maxHeight: 800 }}
/>

View File

@@ -16,13 +16,14 @@ import { useSharedValue, withDelay, withSpring } from 'react-native-reanimated'
import type { SharedValue } from 'react-native-reanimated'
import { runOnJS } from 'react-native-worklets'
import { usePrevious, useSkip } from '../../../hooks/player/callbacks'
import { usePlayerQueueStore } from '../../../stores/player/queue'
import { useCurrentTrack, usePlayerQueueStore } from '../../../stores/player/queue'
import { useNowPlaying } from 'react-native-nitro-player'
import JellifyTrack from '../../../types/JellifyTrack'
import { useApi } from '../../../stores'
import { formatArtistNames } from '../../../utils/formatting/artist-names'
import { isExplicit } from '../../../utils/trackDetails'
import { triggerHaptic } from '../../../hooks/use-haptic-feedback'
import { MediaSourceInfo, NameGuidPair } from '@jellyfin/sdk/lib/generated-client'
type SongInfoProps = {
// Shared animated value coming from Player to drive overlay icons
@@ -67,28 +68,20 @@ export default function SongInfo({ swipeX }: SongInfoProps = {}): React.JSX.Elem
}
})
const playerState = useNowPlaying()
const currentTrack = playerState.currentTrack
const queue = usePlayerQueueStore((state) => state.queue)
// Find the full JellifyTrack in the queue by ID
const nowPlaying = currentTrack
? ((queue.find((t) => t.id === currentTrack.id) as JellifyTrack | undefined) ?? undefined)
: undefined
const currentTrack = useCurrentTrack()
const { data: album } = useQuery({
queryKey: [QueryKeys.Album, nowPlaying?.item.AlbumId],
queryFn: () => fetchItem(api, nowPlaying!.item.AlbumId!),
enabled: !!nowPlaying?.item.AlbumId && !!api,
queryKey: [QueryKeys.Album, currentTrack?.extraPayload?.AlbumId],
queryFn: () => fetchItem(api, currentTrack!.extraPayload!.AlbumId! as string),
enabled: !!currentTrack?.extraPayload?.AlbumId && !!api,
})
// Memoize expensive computations
const trackTitle = nowPlaying?.title ?? 'Untitled Track'
const trackTitle = currentTrack?.title ?? 'Untitled Track'
const { artistItems, artists } = {
artistItems: nowPlaying?.item.ArtistItems,
artists: formatArtistNames(
nowPlaying?.item.ArtistItems?.map((artist) => getItemName(artist)) ?? [],
),
artistItems: currentTrack?.extraPayload?.ArtistItems as NameGuidPair[],
artists: currentTrack?.artist,
}
const handleTrackPress = () => {
@@ -117,7 +110,7 @@ export default function SongInfo({ swipeX }: SongInfoProps = {}): React.JSX.Elem
<TextTicker
{...TextTickerConfig}
style={{ height: getToken('$9') }}
key={`${nowPlaying?.id ?? 'no-track'}-title`}
key={`${currentTrack?.id ?? 'no-track'}-title`}
>
<Text bold fontSize={'$6'} onPress={handleTrackPress}>
{trackTitle}
@@ -127,12 +120,12 @@ export default function SongInfo({ swipeX }: SongInfoProps = {}): React.JSX.Elem
<TextTicker
{...TextTickerConfig}
style={{ height: getToken('$8') }}
key={`${nowPlaying?.id ?? 'no-track'}-artist`}
key={`${currentTrack?.id ?? 'no-track'}-artist`}
>
<Text fontSize={'$6'} color={'$color'} onPress={handleArtistPress}>
{nowPlaying?.artist ?? 'Unknown Artist'}
{currentTrack?.artist ?? 'Unknown Artist'}
</Text>
{isExplicit(nowPlaying) && (
{isExplicit(currentTrack) && (
<XStack alignSelf='center' paddingTop={5.3} paddingLeft='$1'>
<Icon name='alpha-e-box-outline' color={'$color'} xsmall />
</XStack>
@@ -144,22 +137,34 @@ export default function SongInfo({ swipeX }: SongInfoProps = {}): React.JSX.Elem
<Icon
name='dots-horizontal-circle-outline'
onPress={() =>
nowPlaying &&
currentTrack &&
navigationRef.navigate('Context', {
item: nowPlaying.item,
item: {
Id: currentTrack?.id,
Name: currentTrack?.title,
},
streamingMediaSourceInfo:
nowPlaying.sourceType === 'stream'
? nowPlaying.mediaSourceInfo
currentTrack.extraPayload?.sourceType === 'stream'
? (currentTrack.extraPayload
?.mediaSourceInfo as MediaSourceInfo)
: undefined,
downloadedMediaSourceInfo:
nowPlaying.sourceType === 'download'
? nowPlaying.mediaSourceInfo
currentTrack.extraPayload?.sourceType === 'download'
? (currentTrack.extraPayload
?.mediaSourceInfo as MediaSourceInfo)
: undefined,
})
}
/>
{nowPlaying && <FavoriteButton item={nowPlaying.item} />}
{currentTrack && currentTrack.extraPayload && (
<FavoriteButton
item={{
Id: currentTrack.id,
Name: currentTrack.title,
}}
/>
)}
</XStack>
</XStack>
)

View File

@@ -95,17 +95,20 @@ export default function Miniplayer(): React.JSX.Element | null {
<MiniPlayerProgress />
<XStack alignItems='center' padding={'$2'}>
<YStack justify='center' alignItems='center'>
{/* <Animated.View
<Animated.View
entering={FadeIn.easing(Easing.in(Easing.ease))}
exiting={FadeOut.easing(Easing.out(Easing.ease))}
>
<ItemImage
item={nowPlaying!.item}
item={{
Name: nowPlaying!.title,
Id: nowPlaying!.id,
}}
width={'$11'}
height={'$11'}
imageOptions={{ maxWidth: 120, maxHeight: 120 }}
/>
</Animated.View> */}
</Animated.View>
</YStack>
<YStack

View File

@@ -7,7 +7,7 @@ import JellifyTrack, {
import { createVersionedMmkvStorage } from '../../constants/versioned-storage'
import { create } from 'zustand'
import { devtools, persist, PersistStorage, StorageValue } from 'zustand/middleware'
import { RepeatMode, useNowPlaying } from 'react-native-nitro-player'
import { RepeatMode, TrackItem, useNowPlaying } from 'react-native-nitro-player'
import { useShallow } from 'zustand/react/shallow'
/**
@@ -26,32 +26,19 @@ type PlayerQueueStore = {
queueRef: Queue
setQueueRef: (queueRef: Queue) => void
unShuffledQueue: JellifyTrack[]
setUnshuffledQueue: (unShuffledQueue: JellifyTrack[]) => void
unShuffledQueue: TrackItem[]
setUnshuffledQueue: (unShuffledQueue: TrackItem[]) => void
queue: JellifyTrack[]
setQueue: (queue: JellifyTrack[]) => void
queue: TrackItem[]
setQueue: (queue: TrackItem[]) => void
currentTrack: JellifyTrack | undefined
setCurrentTrack: (track: JellifyTrack | undefined) => void
currentTrack: TrackItem | undefined
setCurrentTrack: (track: TrackItem | undefined) => void
currentIndex: number | undefined
setCurrentIndex: (index: number | undefined) => void
}
/**
* Persisted state shape - uses slimmed track types to reduce storage size
*/
type PersistedPlayerQueueState = {
shuffled: boolean
repeatMode: RepeatMode
queueRef: Queue
unShuffledQueue: PersistedJellifyTrack[]
queue: PersistedJellifyTrack[]
currentTrack: PersistedJellifyTrack | undefined
currentIndex: number | undefined
}
/**
* Custom storage that serializes/deserializes tracks to their slim form
* This prevents the "RangeError: String length exceeds limit" error
@@ -63,7 +50,7 @@ const queueStorage: PersistStorage<PlayerQueueStore> = {
if (!str) return null
try {
const parsed = JSON.parse(str) as StorageValue<PersistedPlayerQueueState>
const parsed = JSON.parse(str) as StorageValue<PlayerQueueStore>
const state = parsed.state
// Hydrate persisted tracks back to full JellifyTrack format
@@ -71,11 +58,9 @@ const queueStorage: PersistStorage<PlayerQueueStore> = {
...parsed,
state: {
...state,
queue: (state.queue ?? []).map(fromPersistedTrack),
unShuffledQueue: (state.unShuffledQueue ?? []).map(fromPersistedTrack),
currentTrack: state.currentTrack
? fromPersistedTrack(state.currentTrack)
: undefined,
queue: state.queue ?? [],
unShuffledQueue: state.unShuffledQueue ?? [],
currentTrack: state.currentTrack,
} as unknown as PlayerQueueStore,
}
} catch (e) {
@@ -88,20 +73,14 @@ const queueStorage: PersistStorage<PlayerQueueStore> = {
const state = value.state
// Slim down tracks before persisting to prevent storage overflow
const persistedState: PersistedPlayerQueueState = {
shuffled: state.shuffled,
repeatMode: state.repeatMode,
queueRef: state.queueRef,
const persistedState = {
...state,
// Limit queue size to prevent storage overflow
queue: (state.queue ?? []).slice(0, MAX_PERSISTED_QUEUE_SIZE).map(toPersistedTrack),
unShuffledQueue: (state.unShuffledQueue ?? [])
.slice(0, MAX_PERSISTED_QUEUE_SIZE)
.map(toPersistedTrack),
currentTrack: state.currentTrack ? toPersistedTrack(state.currentTrack) : undefined,
currentIndex: state.currentIndex,
queue: (state.queue ?? []).slice(0, MAX_PERSISTED_QUEUE_SIZE),
unShuffledQueue: (state.unShuffledQueue ?? []).slice(0, MAX_PERSISTED_QUEUE_SIZE),
}
const toStore: StorageValue<PersistedPlayerQueueState> = {
const toStore: StorageValue<PlayerQueueStore> = {
...value,
state: persistedState,
}
@@ -131,19 +110,19 @@ export const usePlayerQueueStore = create<PlayerQueueStore>()(
}),
unShuffledQueue: [],
setUnshuffledQueue: (unShuffledQueue: JellifyTrack[]) =>
setUnshuffledQueue: (unShuffledQueue: TrackItem[]) =>
set({
unShuffledQueue,
}),
queue: [],
setQueue: (queue: JellifyTrack[]) =>
setQueue: (queue: TrackItem[]) =>
set({
queue,
}),
currentTrack: undefined,
setCurrentTrack: (currentTrack: JellifyTrack | undefined) =>
setCurrentTrack: (currentTrack: TrackItem | undefined) =>
set({
currentTrack,
}),

View File

@@ -1,6 +1,10 @@
import { TrackItem } from 'react-native-nitro-player'
import { QueuingType } from '../enums/queuing-type'
import { BaseItemDto, MediaSourceInfo } from '@jellyfin/sdk/lib/generated-client/models'
import {
BaseItemDto,
MediaSourceInfo,
NameGuidPair,
} from '@jellyfin/sdk/lib/generated-client/models'
export type SourceType = 'stream' | 'download'
@@ -18,6 +22,41 @@ export type BaseItemDtoSlimified = Pick<
| 'CustomRating'
>
/**
* Type-safe representation of extra metadata attached to a track.
* This ensures consistent typing when accessing track extraPayload throughout the app.
*
* Note: Properties that come from the API may be null, so they're typed with null | undefined
* to match the source data. When accessing these values, use optional chaining (?.) and
* nullish coalescing (??) to handle both null and undefined safely.
*/
export type TrackExtraPayload = Record<string, unknown> & {
/** List of artist items associated with the track */
artistItems?: NameGuidPair[] | null | undefined
/** Album information for the track */
albumItem?:
| {
Id?: string | null | undefined
Album?: string | null | undefined
}
| undefined
/** Playback source type (streaming or downloaded) */
sourceType?: SourceType | undefined
/** Media source information for detailed codec/quality info */
mediaSourceInfo?: MediaSourceInfo | undefined
/** Official rating for content (e.g. "G", "PG", "M") */
officialRating?: string | null | undefined
/** Custom rating applied by server/admin (e.g. "Adults Only") */
customRating?: string | null | undefined
/** Album ID for looking up album details */
AlbumId?: string | null | undefined
/** Artist items - accessible by alternative key name */
ArtistItems?: NameGuidPair[] | null | undefined
}
/**
* @deprecated Use {@link TrackItem} directly
*/
interface JellifyTrack extends TrackItem {
description?: string | undefined
genre?: string | undefined
@@ -39,6 +78,22 @@ interface JellifyTrack extends TrackItem {
QueuingType?: QueuingType | undefined
}
/**
* Get the extra payload from a track with proper typing.
* This ensures type-safe access to the extraPayload field which comes from react-native-nitro-player.
*
* @param track The track to get the extra payload from
* @returns The properly typed extra payload, or undefined
*
* @example
* const payload = getTrackExtraPayload(currentTrack);
* const artists = payload?.artistItems;
* const albumId = payload?.AlbumId;
*/
export function getTrackExtraPayload(track: TrackItem | undefined): TrackExtraPayload | undefined {
return track?.extraPayload as TrackExtraPayload | undefined
}
/**
* A slimmed-down version of JellifyTrack for persistence.
* Excludes large fields like mediaSourceInfo and transient data

View File

@@ -5,7 +5,7 @@ import {
MediaSourceInfo,
PlaybackInfoResponse,
} from '@jellyfin/sdk/lib/generated-client/models'
import JellifyTrack from '../../types/JellifyTrack'
import JellifyTrack, { TrackExtraPayload } from '../../types/JellifyTrack'
import { QueuingType } from '../../enums/queuing-type'
import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
import { AudioApi } from '@jellyfin/sdk/lib/generated-client/api'
@@ -22,6 +22,8 @@ import StreamingQuality from '../../enums/audio-quality'
import { getAudioCache } from '../../api/mutations/download/offlineModeUtils'
import RNFS from 'react-native-fs'
import { getApi } from '../../stores'
import { TrackItem } from 'react-native-nitro-player'
import { formatArtistNames } from '../formatting/artist-names'
/**
* Ensures a valid session ID is returned.
@@ -120,7 +122,7 @@ export function mapDtoToTrack(
item: BaseItemDto,
deviceProfile: DeviceProfile,
queuingType?: QueuingType,
): JellifyTrack {
): TrackItem {
const api = getApi()!
const downloadedTracks = getAudioCache()
@@ -164,16 +166,34 @@ export function mapDtoToTrack(
? { AUTHORIZATION: (api as Api).accessToken }
: undefined
// Build extraPayload - omit undefined values to avoid native serialization issues
const extraPayload: TrackExtraPayload = {}
if (item.ArtistItems) extraPayload.artistItems = item.ArtistItems
if (item.AlbumId) extraPayload.AlbumId = item.AlbumId
if (item.AlbumId || item.Album) {
extraPayload.albumItem = {
...(item.AlbumId && { Id: item.AlbumId }),
...(item.Album && { Album: item.Album }),
}
}
if (trackMediaInfo.sourceType) extraPayload.sourceType = trackMediaInfo.sourceType
if (trackMediaInfo.mediaSourceInfo)
extraPayload.mediaSourceInfo = trackMediaInfo.mediaSourceInfo
if (item.OfficialRating) extraPayload.officialRating = item.OfficialRating
if (item.CustomRating) extraPayload.customRating = item.CustomRating
return {
...(headers ? { headers } : {}),
...trackMediaInfo,
id: item.Id,
title: item.Name,
artist: formatArtistNames(item.Artists),
album: item.Album,
artist: item.Artists?.join(' • '),
duration: trackMediaInfo.duration,
url: trackMediaInfo.url,
artwork: trackMediaInfo.artwork,
QueuingType: queuingType ?? QueuingType.DirectlyQueued,
} as JellifyTrack
...(Object.keys(extraPayload).length > 0 && { extraPayload }),
} as TrackItem
}
function ensureFileUri(path?: string): string | undefined {

View File

@@ -0,0 +1,117 @@
/**
* Type-safe utilities for accessing extraPayload data from tracks.
* This module provides helper functions to safely access and type the extraPayload field.
*/
import { TrackExtraPayload, getTrackExtraPayload } from '../types/JellifyTrack'
import { NameGuidPair, MediaSourceInfo } from '@jellyfin/sdk/lib/generated-client/models'
import { SourceType } from '../types/JellifyTrack'
import { TrackItem } from 'react-native-nitro-player'
/**
* Get the artist items from a track's extra payload.
*
* @param track The track to get artist items from
* @returns Array of artist items, or undefined if not available
*/
export function getTrackArtists(track: TrackItem | undefined): NameGuidPair[] | undefined {
const payload = getTrackExtraPayload(track)
return (payload?.artistItems ?? payload?.ArtistItems) || undefined
}
/**
* Get the album ID from a track's extra payload.
*
* @param track The track to get album ID from
* @returns The album ID, or undefined if not available
*/
export function getTrackAlbumId(track: TrackItem | undefined): string | undefined {
const payload = getTrackExtraPayload(track)
return payload?.AlbumId ?? undefined
}
/**
* Get the album information from a track's extra payload.
*
* @param track The track to get album info from
* @returns Object with album Id and Album name, or undefined if not available
*/
export function getTrackAlbumInfo(
track: TrackItem | undefined,
): { Id?: string; Album?: string } | undefined {
const payload = getTrackExtraPayload(track)
const albumItem = payload?.albumItem
// Return undefined if no albumItem exists or if it has no useful data
if (!albumItem || (!albumItem.Id && !albumItem.Album)) return undefined
return {
...(albumItem.Id && { Id: albumItem.Id }),
...(albumItem.Album && { Album: albumItem.Album }),
}
}
/**
* Get the source type from a track's extra payload.
*
* @param track The track to get source type from
* @returns The source type ('stream' or 'download'), or undefined if not available
*/
export function getTrackSourceType(track: TrackItem | undefined): SourceType | undefined {
const payload = getTrackExtraPayload(track)
return payload?.sourceType
}
/**
* Get the media source information from a track's extra payload.
*
* @param track The track to get media source info from
* @returns The media source info, or undefined if not available
*/
export function getTrackMediaSourceInfo(track: TrackItem | undefined): MediaSourceInfo | undefined {
const payload = getTrackExtraPayload(track)
return payload?.mediaSourceInfo
}
/**
* Get the official rating from a track's extra payload.
*
* @param track The track to get rating from
* @returns The official rating (e.g. "G", "PG", "M"), or undefined if not available
*/
export function getTrackOfficialRating(track: TrackItem | undefined): string | undefined {
const payload = getTrackExtraPayload(track)
return payload?.officialRating ?? undefined
}
/**
* Get the custom rating from a track's extra payload.
*
* @param track The track to get custom rating from
* @returns The custom rating, or undefined if not available
*/
export function getTrackCustomRating(track: TrackItem | undefined): string | undefined {
const payload = getTrackExtraPayload(track)
return payload?.customRating ?? undefined
}
/**
* Get both official and custom ratings from a track's extra payload.
* Prioritizes official rating if available.
*
* @param track The track to get ratings from
* @returns The first available rating (official or custom), or undefined if neither available
*/
export function getTrackRating(track: TrackItem | undefined): string | undefined {
return getTrackOfficialRating(track) ?? getTrackCustomRating(track)
}
/**
* Get the extra payload with full type safety.
*
* @param track The track to get extra payload from
* @returns The properly typed extra payload, or undefined if track is undefined
*/
export function getTypedExtraPayload(track: TrackItem | undefined): TrackExtraPayload | undefined {
return getTrackExtraPayload(track)
}

View File

@@ -1,6 +1,7 @@
import JellifyTrack from '@/src/types/JellifyTrack'
import { ValueType } from 'react-native-nitro-modules'
import { TrackItem } from 'react-native-nitro-player'
export function isExplicit(nowPlaying: JellifyTrack | undefined) {
export function isExplicit(nowPlaying: TrackItem | undefined) {
if (!nowPlaying) return false
const ADULT_RATINGS = new Set([
'R',
@@ -48,5 +49,8 @@ export function isExplicit(nowPlaying: JellifyTrack | undefined) {
return false
}
return isExplicitByRating(nowPlaying?.officialRating || nowPlaying?.customRating)
return isExplicitByRating(
(nowPlaying?.extraPayload?.officialRating as string) ||
(nowPlaying?.extraPayload?.customRating as string),
)
}