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