From cc0befeb522b75c0055b05425eb745cf6f04d430 Mon Sep 17 00:00:00 2001 From: skalthoff <32023561+skalthoff@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:58:47 -0800 Subject: [PATCH] defer non-critical startup operations - delay StorageProvider queries until 2s after initial render - defer player queue restoration with InteractionManager - add enabled option to download query hooks --- src/api/queries/download/index.ts | 13 +++- src/providers/Player/utils/initialization.ts | 81 +++++++++++--------- src/providers/Storage/index.tsx | 21 ++++- 3 files changed, 73 insertions(+), 42 deletions(-) diff --git a/src/api/queries/download/index.ts b/src/api/queries/download/index.ts index a1e945ef..fddca496 100644 --- a/src/api/queries/download/index.ts +++ b/src/api/queries/download/index.ts @@ -3,13 +3,22 @@ import { useQuery } from '@tanstack/react-query' import fetchStorageInUse from './utils/storage-in-use' import { AUDIO_CACHE_QUERY } from './constants' -export const useStorageInUse = () => +type QueryOptions = { + enabled?: boolean +} + +export const useStorageInUse = (options?: QueryOptions) => useQuery({ queryKey: [QueryKeys.StorageInUse], queryFn: fetchStorageInUse, + enabled: options?.enabled, }) -export const useAllDownloadedTracks = () => useQuery(AUDIO_CACHE_QUERY) +export const useAllDownloadedTracks = (options?: QueryOptions) => + useQuery({ + ...AUDIO_CACHE_QUERY, + enabled: options?.enabled, + }) export const useDownloadedTracks = (itemIds: (string | null | undefined)[]) => useAllDownloadedTracks().data?.filter((download) => itemIds.includes(download.item.Id)) diff --git a/src/providers/Player/utils/initialization.ts b/src/providers/Player/utils/initialization.ts index 3490ed28..40ff0b5d 100644 --- a/src/providers/Player/utils/initialization.ts +++ b/src/providers/Player/utils/initialization.ts @@ -1,48 +1,55 @@ import { isUndefined } from 'lodash' +import { InteractionManager } from 'react-native' import TrackPlayer, { RepeatMode } from 'react-native-track-player' import { usePlayerQueueStore } from '../../../stores/player/queue' import { createMMKV } from 'react-native-mmkv' -export default async function Initialize() { - const { - queue: persistedQueue, - currentIndex: persistedIndex, - repeatMode, - } = usePlayerQueueStore.getState() +export default function Initialize() { + // Defer queue restoration until after UI interactions complete + // This allows the home screen to render faster on cold start + InteractionManager.runAfterInteractions(async () => { + const { + queue: persistedQueue, + currentIndex: persistedIndex, + repeatMode, + } = usePlayerQueueStore.getState() - // Read saved position BEFORE reset() to prevent it from being cleared - const progressStorage = createMMKV({ id: 'progress_storage' }) - const savedPosition = progressStorage.getNumber('player-key') ?? 0 - console.log('savedPosition before reset', savedPosition) + // Read saved position BEFORE reset() to prevent it from being cleared + const progressStorage = createMMKV({ id: 'progress_storage' }) + const savedPosition = progressStorage.getNumber('player-key') ?? 0 + console.log('savedPosition before reset', savedPosition) - const storedPlayQueue = persistedQueue.length > 0 ? persistedQueue : undefined - const storedIndex = persistedIndex + const storedPlayQueue = persistedQueue.length > 0 ? persistedQueue : undefined + const storedIndex = persistedIndex - if ( - Array.isArray(storedPlayQueue) && - storedPlayQueue.length > 0 && - !isUndefined(storedIndex) && - storedIndex !== null - ) { - await TrackPlayer.reset() - await TrackPlayer.add(storedPlayQueue) - await TrackPlayer.skip(storedIndex) + if ( + Array.isArray(storedPlayQueue) && + storedPlayQueue.length > 0 && + !isUndefined(storedIndex) && + storedIndex !== null + ) { + await TrackPlayer.reset() + await TrackPlayer.add(storedPlayQueue) + await TrackPlayer.skip(storedIndex) - usePlayerQueueStore.getState().setQueue(storedPlayQueue) - usePlayerQueueStore.getState().setCurrentIndex(storedIndex) - usePlayerQueueStore.getState().setCurrentTrack(storedPlayQueue[storedIndex] ?? undefined) - } - - const restoredRepeatMode = repeatMode ?? RepeatMode.Off - await TrackPlayer.setRepeatMode(restoredRepeatMode) - - // Restore saved playback position after queue is loaded - if (savedPosition > 0) { - try { - await TrackPlayer.seekTo(savedPosition) - console.log('Restored playback position:', savedPosition) - } catch (error) { - console.warn('Failed to restore playback position:', error) + usePlayerQueueStore.getState().setQueue(storedPlayQueue) + usePlayerQueueStore.getState().setCurrentIndex(storedIndex) + usePlayerQueueStore + .getState() + .setCurrentTrack(storedPlayQueue[storedIndex] ?? undefined) } - } + + const restoredRepeatMode = repeatMode ?? RepeatMode.Off + await TrackPlayer.setRepeatMode(restoredRepeatMode) + + // Restore saved playback position after queue is loaded + if (savedPosition > 0) { + try { + await TrackPlayer.seekTo(savedPosition) + console.log('Restored playback position:', savedPosition) + } catch (error) { + console.warn('Failed to restore playback position:', error) + } + } + }) } diff --git a/src/providers/Storage/index.tsx b/src/providers/Storage/index.tsx index 58af2c57..dd888592 100644 --- a/src/providers/Storage/index.tsx +++ b/src/providers/Storage/index.tsx @@ -1,4 +1,11 @@ -import React, { PropsWithChildren, createContext, use, useContext, useState } from 'react' +import React, { + PropsWithChildren, + createContext, + use, + useContext, + useState, + useEffect, +} from 'react' import { useAllDownloadedTracks, useStorageInUse } from '../../api/queries/download' import { JellifyDownload, JellifyDownloadProgress } from '../../types/JellifyDownload' import { @@ -57,16 +64,24 @@ const sumDownloadBytes = (download: JellifyDownload | undefined) => { } export function StorageProvider({ children }: PropsWithChildren): React.JSX.Element { + // Defer storage queries until after initial app render to improve cold start time + const [shouldFetch, setShouldFetch] = useState(false) + + useEffect(() => { + const timer = setTimeout(() => setShouldFetch(true), 2000) + return () => clearTimeout(timer) + }, []) + const { data: downloads, refetch: refetchDownloads, isFetching: isFetchingDownloads, - } = useAllDownloadedTracks() + } = useAllDownloadedTracks({ enabled: shouldFetch }) const { data: storageInfo, refetch: refetchStorageInfo, isFetching: isFetchingStorage, - } = useStorageInUse() + } = useStorageInUse({ enabled: shouldFetch }) const activeDownloads = useDownloadProgress() const [selection, setSelection] = useState({})