mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-27 19:33:02 +00:00
implement bounded concurrency for catalog loading and add loading screen timeout
This commit is contained in:
parent
29e5dee001
commit
a05a16f67b
1 changed files with 37 additions and 12 deletions
|
|
@ -74,6 +74,8 @@ import { useScrollToTop } from '../contexts/ScrollToTopContext';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const CATALOG_SETTINGS_KEY = 'catalog_settings';
|
const CATALOG_SETTINGS_KEY = 'catalog_settings';
|
||||||
|
const MAX_CONCURRENT_CATALOG_REQUESTS = 4;
|
||||||
|
const HOME_LOADING_SCREEN_TIMEOUT_MS = 5000;
|
||||||
|
|
||||||
// In-memory cache for catalog settings to avoid repeated MMKV reads
|
// In-memory cache for catalog settings to avoid repeated MMKV reads
|
||||||
let cachedCatalogSettings: Record<string, boolean> | null = null;
|
let cachedCatalogSettings: Record<string, boolean> | null = null;
|
||||||
|
|
@ -134,6 +136,7 @@ const HomeScreen = () => {
|
||||||
const [loadedCatalogCount, setLoadedCatalogCount] = useState(0);
|
const [loadedCatalogCount, setLoadedCatalogCount] = useState(0);
|
||||||
const [hasAddons, setHasAddons] = useState<boolean | null>(null);
|
const [hasAddons, setHasAddons] = useState<boolean | null>(null);
|
||||||
const [hintVisible, setHintVisible] = useState(false);
|
const [hintVisible, setHintVisible] = useState(false);
|
||||||
|
const [loadingScreenTimedOut, setLoadingScreenTimedOut] = useState(false);
|
||||||
const totalCatalogsRef = useRef(0);
|
const totalCatalogsRef = useRef(0);
|
||||||
const [visibleCatalogCount, setVisibleCatalogCount] = useState(5); // Reduced for memory
|
const [visibleCatalogCount, setVisibleCatalogCount] = useState(5); // Reduced for memory
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
@ -185,6 +188,7 @@ const HomeScreen = () => {
|
||||||
if (isFetchingRef.current) return;
|
if (isFetchingRef.current) return;
|
||||||
isFetchingRef.current = true;
|
isFetchingRef.current = true;
|
||||||
|
|
||||||
|
setLoadingScreenTimedOut(false);
|
||||||
setCatalogsLoading(true);
|
setCatalogsLoading(true);
|
||||||
setCatalogs([]);
|
setCatalogs([]);
|
||||||
setLoadedCatalogCount(0);
|
setLoadedCatalogCount(0);
|
||||||
|
|
@ -210,6 +214,7 @@ const HomeScreen = () => {
|
||||||
catalogService.getAllAddons(),
|
catalogService.getAllAddons(),
|
||||||
stremioService.getInstalledAddonsAsync()
|
stremioService.getInstalledAddonsAsync()
|
||||||
]);
|
]);
|
||||||
|
const manifestByAddonId = new Map(addonManifests.map((manifest: any) => [manifest.id, manifest]));
|
||||||
|
|
||||||
// Set hasAddons state based on whether we have any addons - ensure on main thread
|
// Set hasAddons state based on whether we have any addons - ensure on main thread
|
||||||
InteractionManager.runAfterInteractions(() => {
|
InteractionManager.runAfterInteractions(() => {
|
||||||
|
|
@ -220,14 +225,20 @@ const HomeScreen = () => {
|
||||||
let catalogIndex = 0;
|
let catalogIndex = 0;
|
||||||
const catalogQueue: (() => Promise<void>)[] = [];
|
const catalogQueue: (() => Promise<void>)[] = [];
|
||||||
|
|
||||||
// Launch all catalog loaders in parallel
|
// Launch loaders with bounded concurrency to reduce startup pressure
|
||||||
const launchAllCatalogs = () => {
|
const launchCatalogLoaders = () => {
|
||||||
while (catalogQueue.length > 0) {
|
const workerCount = Math.min(MAX_CONCURRENT_CATALOG_REQUESTS, catalogQueue.length);
|
||||||
const catalogLoader = catalogQueue.shift();
|
const workers = Array.from({ length: workerCount }, async () => {
|
||||||
if (catalogLoader) {
|
while (catalogQueue.length > 0) {
|
||||||
catalogLoader();
|
const catalogLoader = catalogQueue.shift();
|
||||||
|
if (!catalogLoader) return;
|
||||||
|
await catalogLoader();
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
void Promise.all(workers).catch((error) => {
|
||||||
|
if (__DEV__) console.warn('[HomeScreen] Catalog loader worker failed:', error);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const addon of addons) {
|
for (const addon of addons) {
|
||||||
|
|
@ -243,7 +254,7 @@ const HomeScreen = () => {
|
||||||
|
|
||||||
const catalogLoader = async () => {
|
const catalogLoader = async () => {
|
||||||
try {
|
try {
|
||||||
const manifest = addonManifests.find((a: any) => a.id === addon.id);
|
const manifest = manifestByAddonId.get(addon.id);
|
||||||
if (!manifest) return;
|
if (!manifest) return;
|
||||||
|
|
||||||
const metas = await stremioService.getCatalog(manifest, catalog.type, catalog.id, 1);
|
const metas = await stremioService.getCatalog(manifest, catalog.type, catalog.id, 1);
|
||||||
|
|
@ -345,8 +356,8 @@ const HomeScreen = () => {
|
||||||
setCatalogs(new Array(catalogIndex).fill(null));
|
setCatalogs(new Array(catalogIndex).fill(null));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start all catalog requests in parallel
|
// Start catalog requests with bounded concurrency
|
||||||
launchAllCatalogs();
|
launchCatalogLoaders();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (__DEV__) console.error('[HomeScreen] Error in progressive catalog loading:', error);
|
if (__DEV__) console.error('[HomeScreen] Error in progressive catalog loading:', error);
|
||||||
InteractionManager.runAfterInteractions(() => {
|
InteractionManager.runAfterInteractions(() => {
|
||||||
|
|
@ -356,14 +367,29 @@ const HomeScreen = () => {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Hard cap for initial home loading spinner.
|
||||||
|
// Keeps Home responsive even if one or more catalog addons are slow.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!(catalogsLoading && loadedCatalogCount === 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setLoadingScreenTimedOut(true);
|
||||||
|
}, HOME_LOADING_SCREEN_TIMEOUT_MS);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [catalogsLoading, loadedCatalogCount]);
|
||||||
|
|
||||||
// Only count feature section as loading if it's enabled in settings
|
// Only count feature section as loading if it's enabled in settings
|
||||||
// For catalogs, we show them progressively, so loading should be false as soon as we have any content
|
// For catalogs, we show them progressively, so loading should be false as soon as we have any content
|
||||||
const isLoading = useMemo(() => {
|
const isLoading = useMemo(() => {
|
||||||
|
if (loadingScreenTimedOut) return false;
|
||||||
// Exit loading as soon as at least one catalog is ready, regardless of featured
|
// Exit loading as soon as at least one catalog is ready, regardless of featured
|
||||||
if (loadedCatalogCount > 0) return false;
|
if (loadedCatalogCount > 0) return false;
|
||||||
const heroLoading = showHeroSection ? featuredLoading : false;
|
const heroLoading = showHeroSection ? featuredLoading : false;
|
||||||
return heroLoading && (catalogsLoading && loadedCatalogCount === 0);
|
return heroLoading && (catalogsLoading && loadedCatalogCount === 0);
|
||||||
}, [showHeroSection, featuredLoading, catalogsLoading, loadedCatalogCount]);
|
}, [loadingScreenTimedOut, showHeroSection, featuredLoading, catalogsLoading, loadedCatalogCount]);
|
||||||
|
|
||||||
// Update global loading state
|
// Update global loading state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -1482,4 +1508,3 @@ const HomeScreenWithFocusSync = (props: any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(HomeScreenWithFocusSync);
|
export default React.memo(HomeScreenWithFocusSync);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue