mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 16:51:57 +00:00
metascreen optimization
This commit is contained in:
parent
057c709b41
commit
1b990aa6ec
2 changed files with 1001 additions and 995 deletions
|
|
@ -344,55 +344,55 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
// The function now returns void, just await to let callbacks fire
|
||||
if (__DEV__) logger.log(`🏁 [${logPrefix}:${sourceName}] Stremio fetching process initiated`);
|
||||
} catch (error) {
|
||||
// Catch errors from the initial call to getStreams (e.g., initialization errors)
|
||||
logger.error(`❌ [${logPrefix}:${sourceName}] Initial call failed:`, error);
|
||||
// Catch errors from the initial call to getStreams (e.g., initialization errors)
|
||||
logger.error(`❌ [${logPrefix}:${sourceName}] Initial call failed:`, error);
|
||||
|
||||
// Remove all addons and scrapers from active fetching since the entire request failed
|
||||
setActiveFetchingScrapers(prev => {
|
||||
// Get both Stremio addon names and local scraper names
|
||||
const stremioAddons = stremioService.getInstalledAddons();
|
||||
const stremioNames = stremioAddons.map(addon => addon.name);
|
||||
// Remove all addons and scrapers from active fetching since the entire request failed
|
||||
setActiveFetchingScrapers(prev => {
|
||||
// Get both Stremio addon names and local scraper names
|
||||
const stremioAddons = stremioService.getInstalledAddons();
|
||||
const stremioNames = stremioAddons.map(addon => addon.name);
|
||||
|
||||
// Get local scraper names
|
||||
localScraperService.getInstalledScrapers().then(localScrapers => {
|
||||
const localScraperNames = localScrapers.filter(s => s.enabled).map(s => s.name);
|
||||
const allNames = [...stremioNames, ...localScraperNames];
|
||||
// Get local scraper names
|
||||
localScraperService.getInstalledScrapers().then(localScrapers => {
|
||||
const localScraperNames = localScrapers.filter(s => s.enabled).map(s => s.name);
|
||||
const allNames = [...stremioNames, ...localScraperNames];
|
||||
|
||||
// Remove all from active fetching
|
||||
setActiveFetchingScrapers(current =>
|
||||
current.filter(name => !allNames.includes(name))
|
||||
);
|
||||
}).catch(() => {
|
||||
// If we can't get local scrapers, just remove Stremio addons
|
||||
setActiveFetchingScrapers(current =>
|
||||
current.filter(name => !stremioNames.includes(name))
|
||||
);
|
||||
});
|
||||
// Remove all from active fetching
|
||||
setActiveFetchingScrapers(current =>
|
||||
current.filter(name => !allNames.includes(name))
|
||||
);
|
||||
}).catch(() => {
|
||||
// If we can't get local scrapers, just remove Stremio addons
|
||||
setActiveFetchingScrapers(current =>
|
||||
current.filter(name => !stremioNames.includes(name))
|
||||
);
|
||||
});
|
||||
|
||||
// Immediately remove Stremio addons (local scrapers will be removed async above)
|
||||
return prev.filter(name => !stremioNames.includes(name));
|
||||
});
|
||||
// Immediately remove Stremio addons (local scrapers will be removed async above)
|
||||
return prev.filter(name => !stremioNames.includes(name));
|
||||
});
|
||||
|
||||
// Update scraper statuses to mark all scrapers as failed
|
||||
setScraperStatuses(prevStatuses => {
|
||||
const stremioAddons = stremioService.getInstalledAddons();
|
||||
// Update scraper statuses to mark all scrapers as failed
|
||||
setScraperStatuses(prevStatuses => {
|
||||
const stremioAddons = stremioService.getInstalledAddons();
|
||||
|
||||
return prevStatuses.map(status => {
|
||||
const isStremioAddon = stremioAddons.some(addon => addon.id === status.id || addon.name === status.name);
|
||||
return prevStatuses.map(status => {
|
||||
const isStremioAddon = stremioAddons.some(addon => addon.id === status.id || addon.name === status.name);
|
||||
|
||||
// Mark both Stremio addons and local scrapers as failed
|
||||
if (isStremioAddon || !status.hasCompleted) {
|
||||
return {
|
||||
...status,
|
||||
isLoading: false,
|
||||
hasCompleted: true,
|
||||
error: error instanceof Error ? error.message : 'Initial request failed',
|
||||
endTime: Date.now()
|
||||
};
|
||||
}
|
||||
return status;
|
||||
});
|
||||
});
|
||||
// Mark both Stremio addons and local scrapers as failed
|
||||
if (isStremioAddon || !status.hasCompleted) {
|
||||
return {
|
||||
...status,
|
||||
isLoading: false,
|
||||
hasCompleted: true,
|
||||
error: error instanceof Error ? error.message : 'Initial request failed',
|
||||
endTime: Date.now()
|
||||
};
|
||||
}
|
||||
return status;
|
||||
});
|
||||
});
|
||||
}
|
||||
// Note: This function completes when getStreams returns, not when all callbacks have fired.
|
||||
// Loading indicators should probably be managed based on callbacks completing.
|
||||
|
|
@ -543,40 +543,46 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
actualId = id;
|
||||
console.log('🔍 [useMetadata] TMDB enrichment disabled, using original TMDB ID:', { actualId });
|
||||
} else {
|
||||
const tmdbId = id.split(':')[1];
|
||||
// For TMDB IDs, we need to handle metadata differently
|
||||
if (type === 'movie') {
|
||||
if (__DEV__) logger.log('Fetching movie details from TMDB for:', tmdbId);
|
||||
const movieDetails = await tmdbService.getMovieDetails(
|
||||
tmdbId,
|
||||
settings.useTmdbLocalizedMetadata ? `${settings.tmdbLanguagePreference || 'en'}-US` : 'en-US'
|
||||
);
|
||||
if (movieDetails) {
|
||||
const imdbId = movieDetails.imdb_id || movieDetails.external_ids?.imdb_id;
|
||||
if (imdbId) {
|
||||
// Use the imdbId for compatibility with the rest of the app
|
||||
actualId = imdbId;
|
||||
setImdbId(imdbId);
|
||||
// Also store the TMDB ID for later use
|
||||
setTmdbId(parseInt(tmdbId));
|
||||
} else {
|
||||
// If no IMDb ID, directly call loadTMDBMovie (create this function if needed)
|
||||
const formattedMovie: StreamingContent = {
|
||||
id: `tmdb:${tmdbId}`,
|
||||
type: 'movie',
|
||||
name: movieDetails.title,
|
||||
poster: tmdbService.getImageUrl(movieDetails.poster_path) || '',
|
||||
banner: tmdbService.getImageUrl(movieDetails.backdrop_path) || '',
|
||||
description: movieDetails.overview || '',
|
||||
year: movieDetails.release_date ? parseInt(movieDetails.release_date.substring(0, 4)) : undefined,
|
||||
genres: movieDetails.genres?.map((g: { name: string }) => g.name) || [],
|
||||
inLibrary: false,
|
||||
};
|
||||
const tmdbId = id.split(':')[1];
|
||||
// For TMDB IDs, we need to handle metadata differently
|
||||
if (type === 'movie') {
|
||||
if (__DEV__) logger.log('Fetching movie details from TMDB for:', tmdbId);
|
||||
const movieDetails = await tmdbService.getMovieDetails(
|
||||
tmdbId,
|
||||
settings.useTmdbLocalizedMetadata ? `${settings.tmdbLanguagePreference || 'en'}-US` : 'en-US'
|
||||
);
|
||||
if (movieDetails) {
|
||||
const imdbId = movieDetails.imdb_id || movieDetails.external_ids?.imdb_id;
|
||||
if (imdbId) {
|
||||
// Use the imdbId for compatibility with the rest of the app
|
||||
actualId = imdbId;
|
||||
setImdbId(imdbId);
|
||||
// Also store the TMDB ID for later use
|
||||
setTmdbId(parseInt(tmdbId));
|
||||
} else {
|
||||
// If no IMDb ID, directly call loadTMDBMovie (create this function if needed)
|
||||
const formattedMovie: StreamingContent = {
|
||||
id: `tmdb:${tmdbId}`,
|
||||
type: 'movie',
|
||||
name: movieDetails.title,
|
||||
poster: tmdbService.getImageUrl(movieDetails.poster_path) || '',
|
||||
banner: tmdbService.getImageUrl(movieDetails.backdrop_path) || '',
|
||||
description: movieDetails.overview || '',
|
||||
year: movieDetails.release_date ? parseInt(movieDetails.release_date.substring(0, 4)) : undefined,
|
||||
genres: movieDetails.genres?.map((g: { name: string }) => g.name) || [],
|
||||
inLibrary: false,
|
||||
};
|
||||
|
||||
// Fetch credits to get director and crew information
|
||||
try {
|
||||
const credits = await tmdbService.getCredits(parseInt(tmdbId), 'movie');
|
||||
if (credits && credits.crew) {
|
||||
// OPTIMIZATION: Fetch credits and logo in parallel instead of sequentially
|
||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
||||
const [creditsResult, logoResult] = await Promise.allSettled([
|
||||
tmdbService.getCredits(parseInt(tmdbId), 'movie'),
|
||||
tmdbService.getContentLogo('movie', tmdbId, preferredLanguage)
|
||||
]);
|
||||
|
||||
// Process credits result
|
||||
if (creditsResult.status === 'fulfilled' && creditsResult.value?.crew) {
|
||||
const credits = creditsResult.value;
|
||||
// Extract directors
|
||||
const directors = credits.crew
|
||||
.filter((person: any) => person.job === 'Director')
|
||||
|
|
@ -597,70 +603,73 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
(formattedMovie as any).creators = writers;
|
||||
(formattedMovie as any).writer = writers;
|
||||
}
|
||||
} else if (creditsResult.status === 'rejected') {
|
||||
logger.error('Failed to fetch credits for movie:', creditsResult.reason);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch credits for movie:', error);
|
||||
}
|
||||
|
||||
// Fetch movie logo from TMDB
|
||||
try {
|
||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
||||
const logoUrl = await tmdbService.getContentLogo('movie', tmdbId, preferredLanguage);
|
||||
formattedMovie.logo = logoUrl || undefined; // TMDB logo or undefined (no addon fallback)
|
||||
if (__DEV__) logger.log(`Successfully fetched logo for movie ${tmdbId} from TMDB`);
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch logo from TMDB:', error);
|
||||
formattedMovie.logo = undefined; // Error means no logo
|
||||
}
|
||||
// Process logo result
|
||||
if (logoResult.status === 'fulfilled') {
|
||||
formattedMovie.logo = logoResult.value || undefined;
|
||||
if (__DEV__) logger.log(`Successfully fetched logo for movie ${tmdbId} from TMDB`);
|
||||
} else {
|
||||
logger.error('Failed to fetch logo from TMDB:', logoResult.reason);
|
||||
formattedMovie.logo = undefined;
|
||||
}
|
||||
|
||||
setMetadata(formattedMovie);
|
||||
cacheService.setMetadata(id, type, formattedMovie);
|
||||
(async () => {
|
||||
const items = await catalogService.getLibraryItems();
|
||||
const isInLib = items.some(item => item.id === id);
|
||||
setInLibrary(isInLib);
|
||||
})();
|
||||
setLoading(false);
|
||||
return;
|
||||
setMetadata(formattedMovie);
|
||||
cacheService.setMetadata(id, type, formattedMovie);
|
||||
(async () => {
|
||||
const items = await catalogService.getLibraryItems();
|
||||
const isInLib = items.some(item => item.id === id);
|
||||
setInLibrary(isInLib);
|
||||
})();
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type === 'series') {
|
||||
// Handle TV shows with TMDB IDs
|
||||
if (__DEV__) logger.log('Fetching TV show details from TMDB for:', tmdbId);
|
||||
try {
|
||||
const showDetails = await tmdbService.getTVShowDetails(
|
||||
parseInt(tmdbId),
|
||||
settings.useTmdbLocalizedMetadata ? `${settings.tmdbLanguagePreference || 'en'}-US` : 'en-US'
|
||||
);
|
||||
if (showDetails) {
|
||||
// Get external IDs to check for IMDb ID
|
||||
const externalIds = await tmdbService.getShowExternalIds(parseInt(tmdbId));
|
||||
const imdbId = externalIds?.imdb_id;
|
||||
} else if (type === 'series') {
|
||||
// Handle TV shows with TMDB IDs
|
||||
if (__DEV__) logger.log('Fetching TV show details from TMDB for:', tmdbId);
|
||||
try {
|
||||
const showDetails = await tmdbService.getTVShowDetails(
|
||||
parseInt(tmdbId),
|
||||
settings.useTmdbLocalizedMetadata ? `${settings.tmdbLanguagePreference || 'en'}-US` : 'en-US'
|
||||
);
|
||||
if (showDetails) {
|
||||
// OPTIMIZATION: Fetch external IDs, credits, and logo in parallel
|
||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
||||
const [externalIdsResult, creditsResult, logoResult] = await Promise.allSettled([
|
||||
tmdbService.getShowExternalIds(parseInt(tmdbId)),
|
||||
tmdbService.getCredits(parseInt(tmdbId), 'series'),
|
||||
tmdbService.getContentLogo('tv', tmdbId, preferredLanguage)
|
||||
]);
|
||||
|
||||
if (imdbId) {
|
||||
// Use the imdbId for compatibility with the rest of the app
|
||||
actualId = imdbId;
|
||||
setImdbId(imdbId);
|
||||
// Also store the TMDB ID for later use
|
||||
setTmdbId(parseInt(tmdbId));
|
||||
} else {
|
||||
// If no IMDb ID, create formatted show from TMDB data
|
||||
const formattedShow: StreamingContent = {
|
||||
id: `tmdb:${tmdbId}`,
|
||||
type: 'series',
|
||||
name: showDetails.name,
|
||||
poster: tmdbService.getImageUrl(showDetails.poster_path) || '',
|
||||
banner: tmdbService.getImageUrl(showDetails.backdrop_path) || '',
|
||||
description: showDetails.overview || '',
|
||||
year: showDetails.first_air_date ? parseInt(showDetails.first_air_date.substring(0, 4)) : undefined,
|
||||
genres: showDetails.genres?.map((g: { name: string }) => g.name) || [],
|
||||
inLibrary: false,
|
||||
};
|
||||
const externalIds = externalIdsResult.status === 'fulfilled' ? externalIdsResult.value : null;
|
||||
const imdbId = externalIds?.imdb_id;
|
||||
|
||||
// Fetch credits to get creators
|
||||
try {
|
||||
const credits = await tmdbService.getCredits(parseInt(tmdbId), 'series');
|
||||
if (credits && credits.crew) {
|
||||
if (imdbId) {
|
||||
// Use the imdbId for compatibility with the rest of the app
|
||||
actualId = imdbId;
|
||||
setImdbId(imdbId);
|
||||
// Also store the TMDB ID for later use
|
||||
setTmdbId(parseInt(tmdbId));
|
||||
} else {
|
||||
// If no IMDb ID, create formatted show from TMDB data
|
||||
const formattedShow: StreamingContent = {
|
||||
id: `tmdb:${tmdbId}`,
|
||||
type: 'series',
|
||||
name: showDetails.name,
|
||||
poster: tmdbService.getImageUrl(showDetails.poster_path) || '',
|
||||
banner: tmdbService.getImageUrl(showDetails.backdrop_path) || '',
|
||||
description: showDetails.overview || '',
|
||||
year: showDetails.first_air_date ? parseInt(showDetails.first_air_date.substring(0, 4)) : undefined,
|
||||
genres: showDetails.genres?.map((g: { name: string }) => g.name) || [],
|
||||
inLibrary: false,
|
||||
};
|
||||
|
||||
// Process credits result (already fetched in parallel)
|
||||
if (creditsResult.status === 'fulfilled' && creditsResult.value?.crew) {
|
||||
const credits = creditsResult.value;
|
||||
// Extract creators
|
||||
const creators = credits.crew
|
||||
.filter((person: any) =>
|
||||
|
|
@ -674,43 +683,40 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
if (creators.length > 0) {
|
||||
(formattedShow as any).creators = creators.slice(0, 3);
|
||||
}
|
||||
} else if (creditsResult.status === 'rejected') {
|
||||
logger.error('Failed to fetch credits for TV show:', creditsResult.reason);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch credits for TV show:', error);
|
||||
|
||||
// Process logo result (already fetched in parallel)
|
||||
if (logoResult.status === 'fulfilled') {
|
||||
formattedShow.logo = logoResult.value || undefined;
|
||||
if (__DEV__) logger.log(`Successfully fetched logo for TV show ${tmdbId} from TMDB`);
|
||||
} else {
|
||||
logger.error('Failed to fetch logo from TMDB:', (logoResult as PromiseRejectedResult).reason);
|
||||
formattedShow.logo = undefined;
|
||||
}
|
||||
|
||||
setMetadata(formattedShow);
|
||||
cacheService.setMetadata(id, type, formattedShow);
|
||||
|
||||
// Load series data (episodes)
|
||||
setTmdbId(parseInt(tmdbId));
|
||||
loadSeriesData().catch((error) => { if (__DEV__) console.error(error); });
|
||||
|
||||
(async () => {
|
||||
const items = await catalogService.getLibraryItems();
|
||||
const isInLib = items.some(item => item.id === id);
|
||||
setInLibrary(isInLib);
|
||||
})();
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch TV show logo from TMDB
|
||||
try {
|
||||
const preferredLanguage = settings.tmdbLanguagePreference || 'en';
|
||||
const logoUrl = await tmdbService.getContentLogo('tv', tmdbId, preferredLanguage);
|
||||
formattedShow.logo = logoUrl || undefined; // TMDB logo or undefined (no addon fallback)
|
||||
if (__DEV__) logger.log(`Successfully fetched logo for TV show ${tmdbId} from TMDB`);
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch logo from TMDB:', error);
|
||||
formattedShow.logo = undefined; // Error means no logo
|
||||
}
|
||||
|
||||
setMetadata(formattedShow);
|
||||
cacheService.setMetadata(id, type, formattedShow);
|
||||
|
||||
// Load series data (episodes)
|
||||
setTmdbId(parseInt(tmdbId));
|
||||
loadSeriesData().catch((error) => { if (__DEV__) console.error(error); });
|
||||
|
||||
(async () => {
|
||||
const items = await catalogService.getLibraryItems();
|
||||
const isInLib = items.some(item => item.id === id);
|
||||
setInLibrary(isInLib);
|
||||
})();
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch TV show details from TMDB:', error);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch TV show details from TMDB:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load all data in parallel
|
||||
|
|
@ -728,7 +734,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
withRetry(async () => {
|
||||
console.log('🔍 [useMetadata] Calling catalogService.getEnhancedContentDetails:', { type, actualId, addonId });
|
||||
const result = await withTimeout(
|
||||
catalogService.getEnhancedContentDetails(type, actualId, addonId),
|
||||
catalogService.getEnhancedContentDetails(type, actualId, addonId),
|
||||
API_TIMEOUT
|
||||
);
|
||||
// Store the actual ID used (could be IMDB)
|
||||
|
|
@ -772,7 +778,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
const [content, castData] = await Promise.allSettled([
|
||||
withRetry(async () => {
|
||||
const result = await withTimeout(
|
||||
catalogService.getEnhancedContentDetails(type, stremioId, addonId),
|
||||
catalogService.getEnhancedContentDetails(type, stremioId, addonId),
|
||||
API_TIMEOUT
|
||||
);
|
||||
if (stremioId.startsWith('tt')) {
|
||||
|
|
@ -844,8 +850,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
};
|
||||
const productionInfo = Array.isArray(localized.production_companies)
|
||||
? localized.production_companies
|
||||
.map((c: any) => ({ id: c?.id, name: c?.name, logo: tmdbSvc.getImageUrl(c?.logo_path, 'w185') }))
|
||||
.filter((c: any) => c && (c.logo || c.name))
|
||||
.map((c: any) => ({ id: c?.id, name: c?.name, logo: tmdbSvc.getImageUrl(c?.logo_path, 'w185') }))
|
||||
.filter((c: any) => c && (c.logo || c.name))
|
||||
: [];
|
||||
|
||||
finalMetadata = {
|
||||
|
|
@ -859,7 +865,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
} else { // 'series'
|
||||
const localized = await tmdbSvc.getTVShowDetails(Number(finalTmdbId), lang);
|
||||
if (localized) {
|
||||
const tvDetails = {
|
||||
const tvDetails = {
|
||||
status: localized.status,
|
||||
firstAirDate: localized.first_air_date,
|
||||
lastAirDate: localized.last_air_date,
|
||||
|
|
@ -877,12 +883,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
};
|
||||
const productionInfo = Array.isArray(localized.networks)
|
||||
? localized.networks
|
||||
.map((n: any) => ({
|
||||
id: n?.id,
|
||||
name: n?.name,
|
||||
logo: tmdbSvc.getImageUrl(n?.logo_path, 'w185') || undefined
|
||||
}))
|
||||
.filter((n: any) => n && (n.logo || n.name))
|
||||
.map((n: any) => ({
|
||||
id: n?.id,
|
||||
name: n?.name,
|
||||
logo: tmdbSvc.getImageUrl(n?.logo_path, 'w185') || undefined
|
||||
}))
|
||||
.filter((n: any) => n && (n.logo || n.name))
|
||||
: [];
|
||||
|
||||
finalMetadata = {
|
||||
|
|
@ -1057,7 +1063,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
// Group addon episodes by season
|
||||
const groupedAddonEpisodes: GroupedEpisodes = {};
|
||||
|
||||
addonVideos.forEach((video: any) => {
|
||||
addonVideos.forEach((video: any) => {
|
||||
const seasonNumber = video.season;
|
||||
if (!seasonNumber || seasonNumber < 1) {
|
||||
return; // Skip season 0, which often contains extras
|
||||
|
|
@ -1160,7 +1166,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
overview: data.overview || ep.overview,
|
||||
};
|
||||
}
|
||||
} catch {}
|
||||
} catch { }
|
||||
return ep;
|
||||
})
|
||||
);
|
||||
|
|
@ -1377,27 +1383,27 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
}
|
||||
|
||||
// Initialize scraper tracking
|
||||
try {
|
||||
const allStremioAddons = await stremioService.getInstalledAddons();
|
||||
const localScrapers = await localScraperService.getInstalledScrapers();
|
||||
try {
|
||||
const allStremioAddons = await stremioService.getInstalledAddons();
|
||||
const localScrapers = await localScraperService.getInstalledScrapers();
|
||||
|
||||
// Filter Stremio addons to only include those that provide streams for this content type
|
||||
const streamAddons = allStremioAddons.filter(addon => {
|
||||
if (!addon.resources || !Array.isArray(addon.resources)) {
|
||||
return false;
|
||||
}
|
||||
// Filter Stremio addons to only include those that provide streams for this content type
|
||||
const streamAddons = allStremioAddons.filter(addon => {
|
||||
if (!addon.resources || !Array.isArray(addon.resources)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let hasStreamResource = false;
|
||||
let supportsIdPrefix = false;
|
||||
let hasStreamResource = false;
|
||||
let supportsIdPrefix = false;
|
||||
|
||||
for (const resource of addon.resources) {
|
||||
// Check if the current element is a ResourceObject
|
||||
if (typeof resource === 'object' && resource !== null && 'name' in resource) {
|
||||
const typedResource = resource as any;
|
||||
if (typedResource.name === 'stream' &&
|
||||
Array.isArray(typedResource.types) &&
|
||||
typedResource.types.includes(type)) {
|
||||
hasStreamResource = true;
|
||||
for (const resource of addon.resources) {
|
||||
// Check if the current element is a ResourceObject
|
||||
if (typeof resource === 'object' && resource !== null && 'name' in resource) {
|
||||
const typedResource = resource as any;
|
||||
if (typedResource.name === 'stream' &&
|
||||
Array.isArray(typedResource.types) &&
|
||||
typedResource.types.includes(type)) {
|
||||
hasStreamResource = true;
|
||||
|
||||
// Check if this addon supports the ID prefix generically: any prefix must match start of id
|
||||
if (Array.isArray(typedResource.idPrefixes) && typedResource.idPrefixes.length > 0) {
|
||||
|
|
@ -1406,67 +1412,67 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
// If no idPrefixes specified, assume it supports all prefixes
|
||||
supportsIdPrefix = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check if the element is the simple string "stream" AND the addon has a top-level types array
|
||||
else if (typeof resource === 'string' && resource === 'stream' && addon.types) {
|
||||
if (Array.isArray(addon.types) && addon.types.includes(type)) {
|
||||
hasStreamResource = true;
|
||||
// For simple string resources, check addon-level idPrefixes generically
|
||||
if (addon.idPrefixes && Array.isArray(addon.idPrefixes) && addon.idPrefixes.length > 0) {
|
||||
supportsIdPrefix = addon.idPrefixes.some((p: string) => id.startsWith(p));
|
||||
} else {
|
||||
// If no idPrefixes specified, assume it supports all prefixes
|
||||
supportsIdPrefix = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check if the element is the simple string "stream" AND the addon has a top-level types array
|
||||
else if (typeof resource === 'string' && resource === 'stream' && addon.types) {
|
||||
if (Array.isArray(addon.types) && addon.types.includes(type)) {
|
||||
hasStreamResource = true;
|
||||
// For simple string resources, check addon-level idPrefixes generically
|
||||
if (addon.idPrefixes && Array.isArray(addon.idPrefixes) && addon.idPrefixes.length > 0) {
|
||||
supportsIdPrefix = addon.idPrefixes.some((p: string) => id.startsWith(p));
|
||||
} else {
|
||||
// If no idPrefixes specified, assume it supports all prefixes
|
||||
supportsIdPrefix = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasStreamResource && supportsIdPrefix;
|
||||
});
|
||||
if (__DEV__) console.log('[useMetadata.loadStreams] Eligible stream addons:', streamAddons.map(a => a.id));
|
||||
return hasStreamResource && supportsIdPrefix;
|
||||
});
|
||||
if (__DEV__) console.log('[useMetadata.loadStreams] Eligible stream addons:', streamAddons.map(a => a.id));
|
||||
|
||||
// Initialize scraper statuses for tracking
|
||||
const initialStatuses: ScraperStatus[] = [];
|
||||
const initialActiveFetching: string[] = [];
|
||||
// Initialize scraper statuses for tracking
|
||||
const initialStatuses: ScraperStatus[] = [];
|
||||
const initialActiveFetching: string[] = [];
|
||||
|
||||
// Add stream-capable Stremio addons only
|
||||
streamAddons.forEach(addon => {
|
||||
initialStatuses.push({
|
||||
id: addon.id,
|
||||
name: addon.name,
|
||||
isLoading: true,
|
||||
hasCompleted: false,
|
||||
error: null,
|
||||
startTime: Date.now(),
|
||||
endTime: null
|
||||
});
|
||||
initialActiveFetching.push(addon.name);
|
||||
});
|
||||
|
||||
// Add local scrapers if enabled
|
||||
localScrapers.filter((scraper: ScraperInfo) => scraper.enabled).forEach((scraper: ScraperInfo) => {
|
||||
initialStatuses.push({
|
||||
id: scraper.id,
|
||||
name: scraper.name,
|
||||
isLoading: true,
|
||||
hasCompleted: false,
|
||||
error: null,
|
||||
startTime: Date.now(),
|
||||
endTime: null
|
||||
});
|
||||
initialActiveFetching.push(scraper.name);
|
||||
// Add stream-capable Stremio addons only
|
||||
streamAddons.forEach(addon => {
|
||||
initialStatuses.push({
|
||||
id: addon.id,
|
||||
name: addon.name,
|
||||
isLoading: true,
|
||||
hasCompleted: false,
|
||||
error: null,
|
||||
startTime: Date.now(),
|
||||
endTime: null
|
||||
});
|
||||
initialActiveFetching.push(addon.name);
|
||||
});
|
||||
|
||||
setScraperStatuses(initialStatuses);
|
||||
setActiveFetchingScrapers(initialActiveFetching);
|
||||
console.log('🔍 [loadStreams] Initialized activeFetchingScrapers:', initialActiveFetching);
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Failed to initialize scraper tracking:', error);
|
||||
}
|
||||
// Add local scrapers if enabled
|
||||
localScrapers.filter((scraper: ScraperInfo) => scraper.enabled).forEach((scraper: ScraperInfo) => {
|
||||
initialStatuses.push({
|
||||
id: scraper.id,
|
||||
name: scraper.name,
|
||||
isLoading: true,
|
||||
hasCompleted: false,
|
||||
error: null,
|
||||
startTime: Date.now(),
|
||||
endTime: null
|
||||
});
|
||||
initialActiveFetching.push(scraper.name);
|
||||
});
|
||||
|
||||
setScraperStatuses(initialStatuses);
|
||||
setActiveFetchingScrapers(initialActiveFetching);
|
||||
console.log('🔍 [loadStreams] Initialized activeFetchingScrapers:', initialActiveFetching);
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Failed to initialize scraper tracking:', error);
|
||||
}
|
||||
|
||||
// Start Stremio request using the converted ID format
|
||||
if (__DEV__) console.log('🎬 [loadStreams] Using ID for Stremio addons:', stremioId);
|
||||
|
|
@ -1523,79 +1529,79 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
setAddonResponseOrder([]); // Reset response order
|
||||
|
||||
// Initialize scraper tracking for episodes
|
||||
try {
|
||||
const allStremioAddons = await stremioService.getInstalledAddons();
|
||||
const localScrapers = await localScraperService.getInstalledScrapers();
|
||||
try {
|
||||
const allStremioAddons = await stremioService.getInstalledAddons();
|
||||
const localScrapers = await localScraperService.getInstalledScrapers();
|
||||
|
||||
// Filter Stremio addons to only include those that provide streams for series content
|
||||
const streamAddons = allStremioAddons.filter(addon => {
|
||||
if (!addon.resources || !Array.isArray(addon.resources)) {
|
||||
return false;
|
||||
}
|
||||
// Filter Stremio addons to only include those that provide streams for series content
|
||||
const streamAddons = allStremioAddons.filter(addon => {
|
||||
if (!addon.resources || !Array.isArray(addon.resources)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let hasStreamResource = false;
|
||||
let hasStreamResource = false;
|
||||
|
||||
for (const resource of addon.resources) {
|
||||
// Check if the current element is a ResourceObject
|
||||
if (typeof resource === 'object' && resource !== null && 'name' in resource) {
|
||||
const typedResource = resource as any;
|
||||
if (typedResource.name === 'stream' &&
|
||||
Array.isArray(typedResource.types) &&
|
||||
typedResource.types.includes('series')) {
|
||||
hasStreamResource = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check if the element is the simple string "stream" AND the addon has a top-level types array
|
||||
else if (typeof resource === 'string' && resource === 'stream' && addon.types) {
|
||||
if (Array.isArray(addon.types) && addon.types.includes('series')) {
|
||||
hasStreamResource = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const resource of addon.resources) {
|
||||
// Check if the current element is a ResourceObject
|
||||
if (typeof resource === 'object' && resource !== null && 'name' in resource) {
|
||||
const typedResource = resource as any;
|
||||
if (typedResource.name === 'stream' &&
|
||||
Array.isArray(typedResource.types) &&
|
||||
typedResource.types.includes('series')) {
|
||||
hasStreamResource = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check if the element is the simple string "stream" AND the addon has a top-level types array
|
||||
else if (typeof resource === 'string' && resource === 'stream' && addon.types) {
|
||||
if (Array.isArray(addon.types) && addon.types.includes('series')) {
|
||||
hasStreamResource = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasStreamResource;
|
||||
});
|
||||
return hasStreamResource;
|
||||
});
|
||||
|
||||
// Initialize scraper statuses for tracking
|
||||
const initialStatuses: ScraperStatus[] = [];
|
||||
const initialActiveFetching: string[] = [];
|
||||
// Initialize scraper statuses for tracking
|
||||
const initialStatuses: ScraperStatus[] = [];
|
||||
const initialActiveFetching: string[] = [];
|
||||
|
||||
// Add stream-capable Stremio addons only
|
||||
streamAddons.forEach(addon => {
|
||||
initialStatuses.push({
|
||||
id: addon.id,
|
||||
name: addon.name,
|
||||
isLoading: true,
|
||||
hasCompleted: false,
|
||||
error: null,
|
||||
startTime: Date.now(),
|
||||
endTime: null
|
||||
});
|
||||
initialActiveFetching.push(addon.name);
|
||||
});
|
||||
// Add stream-capable Stremio addons only
|
||||
streamAddons.forEach(addon => {
|
||||
initialStatuses.push({
|
||||
id: addon.id,
|
||||
name: addon.name,
|
||||
isLoading: true,
|
||||
hasCompleted: false,
|
||||
error: null,
|
||||
startTime: Date.now(),
|
||||
endTime: null
|
||||
});
|
||||
initialActiveFetching.push(addon.name);
|
||||
});
|
||||
|
||||
// Add local scrapers if enabled
|
||||
localScrapers.filter((scraper: ScraperInfo) => scraper.enabled).forEach((scraper: ScraperInfo) => {
|
||||
initialStatuses.push({
|
||||
id: scraper.id,
|
||||
name: scraper.name,
|
||||
isLoading: true,
|
||||
hasCompleted: false,
|
||||
error: null,
|
||||
startTime: Date.now(),
|
||||
endTime: null
|
||||
});
|
||||
initialActiveFetching.push(scraper.name);
|
||||
});
|
||||
// Add local scrapers if enabled
|
||||
localScrapers.filter((scraper: ScraperInfo) => scraper.enabled).forEach((scraper: ScraperInfo) => {
|
||||
initialStatuses.push({
|
||||
id: scraper.id,
|
||||
name: scraper.name,
|
||||
isLoading: true,
|
||||
hasCompleted: false,
|
||||
error: null,
|
||||
startTime: Date.now(),
|
||||
endTime: null
|
||||
});
|
||||
initialActiveFetching.push(scraper.name);
|
||||
});
|
||||
|
||||
setScraperStatuses(initialStatuses);
|
||||
setActiveFetchingScrapers(initialActiveFetching);
|
||||
console.log('🔍 [loadEpisodeStreams] Initialized activeFetchingScrapers:', initialActiveFetching);
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Failed to initialize episode scraper tracking:', error);
|
||||
}
|
||||
setScraperStatuses(initialStatuses);
|
||||
setActiveFetchingScrapers(initialActiveFetching);
|
||||
console.log('🔍 [loadEpisodeStreams] Initialized activeFetchingScrapers:', initialActiveFetching);
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Failed to initialize episode scraper tracking:', error);
|
||||
}
|
||||
|
||||
// Get TMDB ID for external sources and determine the correct ID for Stremio addons
|
||||
if (__DEV__) console.log('🔍 [loadEpisodeStreams] Getting TMDB ID for:', id);
|
||||
|
|
@ -1983,12 +1989,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
if (showDetails.networks) {
|
||||
productionInfo = Array.isArray(showDetails.networks)
|
||||
? showDetails.networks
|
||||
.map((n: any) => ({
|
||||
id: n?.id,
|
||||
name: n?.name,
|
||||
logo: tmdbService.getImageUrl(n?.logo_path, 'w185') || undefined,
|
||||
}))
|
||||
.filter((n: any) => n && (n.logo || n.name))
|
||||
.map((n: any) => ({
|
||||
id: n?.id,
|
||||
name: n?.name,
|
||||
logo: tmdbService.getImageUrl(n?.logo_path, 'w185') || undefined,
|
||||
}))
|
||||
.filter((n: any) => n && (n.logo || n.name))
|
||||
: [];
|
||||
}
|
||||
|
||||
|
|
@ -2030,12 +2036,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
if (movieDetails.production_companies) {
|
||||
productionInfo = Array.isArray(movieDetails.production_companies)
|
||||
? movieDetails.production_companies
|
||||
.map((c: any) => ({
|
||||
id: c?.id,
|
||||
name: c?.name,
|
||||
logo: tmdbService.getImageUrl(c?.logo_path, 'w185'),
|
||||
}))
|
||||
.filter((c: any) => c && (c.logo || c.name))
|
||||
.map((c: any) => ({
|
||||
id: c?.id,
|
||||
name: c?.name,
|
||||
logo: tmdbService.getImageUrl(c?.logo_path, 'w185'),
|
||||
}))
|
||||
.filter((c: any) => c && (c.logo || c.name))
|
||||
: [];
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue