mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
addon stream fetching imrpovements
This commit is contained in:
parent
bbbc22f30f
commit
07eab50848
4 changed files with 128 additions and 112 deletions
|
|
@ -870,19 +870,6 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
let hasStreamResource = false;
|
||||
let supportsIdPrefix = false;
|
||||
|
||||
// Extract ID prefix from the ID
|
||||
let idPrefix = id.split(':')[0];
|
||||
|
||||
// For IMDb IDs (tt...), extract just the 'tt' prefix
|
||||
if (idPrefix.startsWith('tt')) {
|
||||
idPrefix = 'tt';
|
||||
}
|
||||
// For Kitsu IDs, keep the full prefix
|
||||
else if (idPrefix === 'kitsu') {
|
||||
idPrefix = 'kitsu';
|
||||
}
|
||||
// For other prefixes, keep as is
|
||||
|
||||
for (const resource of addon.resources) {
|
||||
// Check if the current element is a ResourceObject
|
||||
if (typeof resource === 'object' && resource !== null && 'name' in resource) {
|
||||
|
|
@ -892,13 +879,13 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
typedResource.types.includes(type)) {
|
||||
hasStreamResource = true;
|
||||
|
||||
// Check if this addon supports the ID prefix
|
||||
if (Array.isArray(typedResource.idPrefixes)) {
|
||||
supportsIdPrefix = typedResource.idPrefixes.includes(idPrefix);
|
||||
} else {
|
||||
// If no idPrefixes specified, assume it supports all prefixes
|
||||
supportsIdPrefix = 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) {
|
||||
supportsIdPrefix = typedResource.idPrefixes.some((p: string) => id.startsWith(p));
|
||||
} else {
|
||||
// If no idPrefixes specified, assume it supports all prefixes
|
||||
supportsIdPrefix = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -906,9 +893,9 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
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
|
||||
if (addon.idPrefixes && Array.isArray(addon.idPrefixes)) {
|
||||
supportsIdPrefix = addon.idPrefixes.includes(idPrefix);
|
||||
// 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;
|
||||
|
|
@ -920,6 +907,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
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[] = [];
|
||||
|
|
@ -1217,7 +1205,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
// Re-run series data loading when metadata updates with videos
|
||||
useEffect(() => {
|
||||
if (metadata && type === 'series' && metadata.videos && metadata.videos.length > 0) {
|
||||
if (metadata && metadata.videos && metadata.videos.length > 0) {
|
||||
logger.log(`🎬 Metadata updated with ${metadata.videos.length} episodes, reloading series data`);
|
||||
loadSeriesData().catch((error) => { if (__DEV__) console.error(error); });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ const MetadataScreen: React.FC = () => {
|
|||
} = useMetadata({ id, type, addonId });
|
||||
|
||||
// Optimized hooks with memoization and conditional loading
|
||||
const watchProgressData = useWatchProgress(id, type as 'movie' | 'series', episodeId, episodes);
|
||||
const watchProgressData = useWatchProgress(id, Object.keys(groupedEpisodes).length > 0 ? 'series' : type as 'movie' | 'series', episodeId, episodes);
|
||||
const assetData = useMetadataAssets(metadata, id, type, imdbId, settings, setMetadata);
|
||||
const animations = useMetadataAnimations(safeAreaTop, watchProgressData.watchProgress);
|
||||
|
||||
|
|
@ -277,9 +277,9 @@ const MetadataScreen: React.FC = () => {
|
|||
item.type === 'movie' &&
|
||||
item.movie?.ids.imdb === id.replace('tt', '')
|
||||
);
|
||||
} else if (type === 'series') {
|
||||
relevantProgress = allProgress.filter(item =>
|
||||
item.type === 'episode' &&
|
||||
} else if (Object.keys(groupedEpisodes).length > 0) {
|
||||
relevantProgress = allProgress.filter(item =>
|
||||
item.type === 'episode' &&
|
||||
item.show?.ids.imdb === id.replace('tt', '')
|
||||
);
|
||||
}
|
||||
|
|
@ -290,7 +290,7 @@ const MetadataScreen: React.FC = () => {
|
|||
if (__DEV__) console.log(`[MetadataScreen] Found ${relevantProgress.length} Trakt progress items for ${type}`);
|
||||
|
||||
// Find most recent progress if multiple episodes
|
||||
if (type === 'series' && relevantProgress.length > 1) {
|
||||
if (Object.keys(groupedEpisodes).length > 0 && relevantProgress.length > 1) {
|
||||
const mostRecent = relevantProgress.sort((a, b) =>
|
||||
new Date(b.paused_at).getTime() - new Date(a.paused_at).getTime()
|
||||
)[0];
|
||||
|
|
@ -411,7 +411,7 @@ const MetadataScreen: React.FC = () => {
|
|||
return ep.stremioId || `${id}:${ep.season_number}:${ep.episode_number}`;
|
||||
};
|
||||
|
||||
if (type === 'series') {
|
||||
if (Object.keys(groupedEpisodes).length > 0) {
|
||||
// Determine if current episode is finished
|
||||
let progressPercent = 0;
|
||||
if (watchProgress && watchProgress.duration > 0) {
|
||||
|
|
@ -581,7 +581,7 @@ const MetadataScreen: React.FC = () => {
|
|||
|
||||
// Show loading screen if metadata is not yet available
|
||||
if (loading || !isContentReady) {
|
||||
return <MetadataLoadingScreen type={type as 'movie' | 'series'} />;
|
||||
return <MetadataLoadingScreen type={Object.keys(groupedEpisodes).length > 0 ? 'series' : type as 'movie' | 'series'} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -634,7 +634,7 @@ const MetadataScreen: React.FC = () => {
|
|||
watchProgressOpacity={animations.watchProgressOpacity}
|
||||
watchProgressWidth={animations.watchProgressWidth}
|
||||
watchProgress={watchProgressData.watchProgress}
|
||||
type={type as 'movie' | 'series'}
|
||||
type={Object.keys(groupedEpisodes).length > 0 ? 'series' : type as 'movie' | 'series'}
|
||||
getEpisodeDetails={watchProgressData.getEpisodeDetails}
|
||||
handleShowStreams={handleShowStreams}
|
||||
handleToggleLibrary={handleToggleLibrary}
|
||||
|
|
@ -655,11 +655,11 @@ const MetadataScreen: React.FC = () => {
|
|||
<MetadataDetails
|
||||
metadata={metadata}
|
||||
imdbId={imdbId}
|
||||
type={type as 'movie' | 'series'}
|
||||
type={Object.keys(groupedEpisodes).length > 0 ? 'series' : type as 'movie' | 'series'}
|
||||
contentId={id}
|
||||
loadingMetadata={false}
|
||||
renderRatings={() => imdbId && shouldLoadSecondaryData ? (
|
||||
<MemoizedRatingsSection imdbId={imdbId} type={type === 'series' ? 'show' : 'movie'} />
|
||||
<MemoizedRatingsSection imdbId={imdbId} type={Object.keys(groupedEpisodes).length > 0 ? 'show' : 'movie'} />
|
||||
) : null}
|
||||
/>
|
||||
|
||||
|
|
@ -681,7 +681,7 @@ const MetadataScreen: React.FC = () => {
|
|||
)}
|
||||
|
||||
{/* Series/Movie Content with episode skeleton when loading */}
|
||||
{type === 'series' ? (
|
||||
{Object.keys(groupedEpisodes).length > 0 ? (
|
||||
<MemoizedSeriesContent
|
||||
episodes={Object.values(groupedEpisodes).flat()}
|
||||
selectedSeason={selectedSeason}
|
||||
|
|
@ -799,16 +799,6 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
// Performance Optimizations Applied:
|
||||
// 1. Memoized components (Cast, Series, Movie, Ratings, Modal)
|
||||
// 2. Lazy loading of secondary data (cast, recommendations, ratings)
|
||||
// 3. Focus-based rendering and interaction management
|
||||
// 4. Debounced Trakt progress fetching with reduced logging
|
||||
// 5. Optimized callback functions with screen focus checks
|
||||
// 6. Conditional haptics feedback based on screen focus
|
||||
// 7. Memory management and cleanup on unmount
|
||||
// 8. Performance monitoring in development mode
|
||||
// 9. Reduced re-renders through better state management
|
||||
// 10. RequestAnimationFrame for navigation optimization
|
||||
|
||||
|
||||
export default MetadataScreen;
|
||||
|
|
@ -666,7 +666,8 @@ export const StreamsScreen = () => {
|
|||
// Skip processing if component is unmounting
|
||||
if (!isMounted.current) return;
|
||||
|
||||
const currentStreamsData = type === 'series' ? episodeStreams : groupedStreams;
|
||||
const currentStreamsData = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
|
||||
if (__DEV__) console.log('[StreamsScreen] streams state changed', { providerKeys: Object.keys(currentStreamsData || {}), type });
|
||||
|
||||
// Update available providers immediately when streams change
|
||||
const providersWithStreams = Object.entries(currentStreamsData)
|
||||
|
|
@ -680,6 +681,7 @@ export const StreamsScreen = () => {
|
|||
// Only update if we have new providers, don't remove existing ones during loading
|
||||
setAvailableProviders(prevProviders => {
|
||||
const newProviders = new Set([...prevProviders, ...providersWithStreamsSet]);
|
||||
if (__DEV__) console.log('[StreamsScreen] availableProviders ->', Array.from(newProviders));
|
||||
return newProviders;
|
||||
});
|
||||
}
|
||||
|
|
@ -701,6 +703,7 @@ export const StreamsScreen = () => {
|
|||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed && __DEV__) console.log('[StreamsScreen] loadingProviders ->', nextLoading);
|
||||
return changed ? nextLoading : prevLoading;
|
||||
});
|
||||
|
||||
|
|
@ -717,7 +720,7 @@ export const StreamsScreen = () => {
|
|||
}
|
||||
|
||||
// Check if provider exists in current streams data
|
||||
const currentStreamsData = type === 'series' ? episodeStreams : groupedStreams;
|
||||
const currentStreamsData = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
|
||||
const hasStreamsForProvider = currentStreamsData[selectedProvider] &&
|
||||
currentStreamsData[selectedProvider].streams &&
|
||||
currentStreamsData[selectedProvider].streams.length > 0;
|
||||
|
|
@ -733,14 +736,19 @@ export const StreamsScreen = () => {
|
|||
// Update useEffect to check for sources
|
||||
useEffect(() => {
|
||||
const checkProviders = async () => {
|
||||
if (__DEV__) console.log('[StreamsScreen] checkProviders() start', { id, type, episodeId, fromPlayer });
|
||||
logger.log(`[StreamsScreen] checkProviders() start id=${id} type=${type} episodeId=${episodeId || 'none'} fromPlayer=${!!fromPlayer}`);
|
||||
// Check for Stremio addons
|
||||
const hasStremioProviders = await stremioService.hasStreamProviders();
|
||||
if (__DEV__) console.log('[StreamsScreen] hasStremioProviders:', hasStremioProviders);
|
||||
|
||||
// Check for local scrapers (only if enabled in settings)
|
||||
const hasLocalScrapers = settings.enableLocalScrapers && await localScraperService.hasScrapers();
|
||||
if (__DEV__) console.log('[StreamsScreen] hasLocalScrapers:', hasLocalScrapers, 'enableLocalScrapers:', settings.enableLocalScrapers);
|
||||
|
||||
// We have providers if we have either Stremio addons OR enabled local scrapers
|
||||
const hasProviders = hasStremioProviders || hasLocalScrapers;
|
||||
logger.log(`[StreamsScreen] provider check: hasProviders=${hasProviders}`);
|
||||
|
||||
if (!isMounted.current) return;
|
||||
|
||||
|
|
@ -748,22 +756,43 @@ export const StreamsScreen = () => {
|
|||
setHasStremioStreamProviders(hasStremioProviders);
|
||||
|
||||
if (!hasProviders) {
|
||||
logger.log('[StreamsScreen] No providers detected; scheduling no-sources UI');
|
||||
const timer = setTimeout(() => {
|
||||
if (isMounted.current) setShowNoSourcesError(true);
|
||||
}, 500);
|
||||
return () => clearTimeout(timer);
|
||||
} else {
|
||||
if (type === 'series' && episodeId) {
|
||||
if ((type === 'series' || type === 'other') && episodeId) {
|
||||
logger.log(`🎬 Loading episode streams for: ${episodeId}`);
|
||||
setLoadingProviders({
|
||||
'stremio': true
|
||||
});
|
||||
setSelectedEpisode(episodeId);
|
||||
setStreamsLoadStart(Date.now());
|
||||
if (__DEV__) console.log('[StreamsScreen] calling loadEpisodeStreams', episodeId);
|
||||
loadEpisodeStreams(episodeId);
|
||||
} else if (type === 'movie') {
|
||||
logger.log(`🎬 Loading movie streams for: ${id}`);
|
||||
setStreamsLoadStart(Date.now());
|
||||
if (__DEV__) console.log('[StreamsScreen] calling loadStreams (movie)', id);
|
||||
loadStreams();
|
||||
} else if ((type === 'series' || type === 'other') && !episodeId) {
|
||||
// Series with no episodes (e.g., TV/live channels) – fetch streams directly
|
||||
logger.log(`🎬 Loading series streams (no episodes) for: ${id}`);
|
||||
setLoadingProviders({
|
||||
'stremio': true
|
||||
});
|
||||
setStreamsLoadStart(Date.now());
|
||||
if (__DEV__) console.log('[StreamsScreen] calling loadStreams (series no episodeId)', id);
|
||||
loadStreams();
|
||||
} else if (type === 'tv') {
|
||||
// TV/live content – fetch streams directly
|
||||
logger.log(`📺 Loading TV streams for: ${id}`);
|
||||
setLoadingProviders({
|
||||
'stremio': true
|
||||
});
|
||||
setStreamsLoadStart(Date.now());
|
||||
if (__DEV__) console.log('[StreamsScreen] calling loadStreams (tv)', id);
|
||||
loadStreams();
|
||||
}
|
||||
|
||||
|
|
@ -972,7 +1001,7 @@ export const StreamsScreen = () => {
|
|||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
|
||||
// Prepare available streams for the change source feature
|
||||
const streamsToPass = type === 'series' ? episodeStreams : groupedStreams;
|
||||
const streamsToPass = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
|
||||
|
||||
// Determine the stream name using the same logic as StreamCard
|
||||
const streamName = stream.name || stream.title || 'Unnamed Stream';
|
||||
|
|
@ -1027,9 +1056,9 @@ export const StreamsScreen = () => {
|
|||
navigation.navigate(playerRoute as any, {
|
||||
uri: stream.url,
|
||||
title: metadata?.name || '',
|
||||
episodeTitle: type === 'series' ? currentEpisode?.name : undefined,
|
||||
season: type === 'series' ? currentEpisode?.season_number : undefined,
|
||||
episode: type === 'series' ? currentEpisode?.episode_number : undefined,
|
||||
episodeTitle: (type === 'series' || type === 'other') ? currentEpisode?.name : undefined,
|
||||
season: (type === 'series' || type === 'other') ? currentEpisode?.season_number : undefined,
|
||||
episode: (type === 'series' || type === 'other') ? currentEpisode?.episode_number : undefined,
|
||||
quality: (stream.title?.match(/(\d+)p/) || [])[1] || undefined,
|
||||
year: metadata?.year,
|
||||
streamProvider: streamProvider,
|
||||
|
|
@ -1040,7 +1069,7 @@ export const StreamsScreen = () => {
|
|||
forceVlc,
|
||||
id,
|
||||
type,
|
||||
episodeId: type === 'series' && selectedEpisode ? selectedEpisode : undefined,
|
||||
episodeId: (type === 'series' || type === 'other') && selectedEpisode ? selectedEpisode : undefined,
|
||||
imdbId: imdbId || undefined,
|
||||
availableStreams: streamsToPass,
|
||||
backdrop: bannerImage || undefined,
|
||||
|
|
@ -1221,8 +1250,8 @@ export const StreamsScreen = () => {
|
|||
const success = await VideoPlayerService.playVideo(stream.url, {
|
||||
useExternalPlayer: true,
|
||||
title: metadata?.name || 'Video',
|
||||
episodeTitle: type === 'series' ? currentEpisode?.name : undefined,
|
||||
episodeNumber: type === 'series' && currentEpisode ? `S${currentEpisode.season_number}E${currentEpisode.episode_number}` : undefined,
|
||||
episodeTitle: (type === 'series' || type === 'other') ? currentEpisode?.name : undefined,
|
||||
episodeNumber: (type === 'series' || type === 'other') && currentEpisode ? `S${currentEpisode.season_number}E${currentEpisode.episode_number}` : undefined,
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
|
|
@ -1280,7 +1309,7 @@ export const StreamsScreen = () => {
|
|||
!autoplayTriggered &&
|
||||
isAutoplayWaiting
|
||||
) {
|
||||
const streams = type === 'series' ? episodeStreams : groupedStreams;
|
||||
const streams = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
|
||||
|
||||
if (Object.keys(streams).length > 0) {
|
||||
const bestStream = getBestStream(streams);
|
||||
|
|
@ -1311,7 +1340,7 @@ export const StreamsScreen = () => {
|
|||
|
||||
const filterItems = useMemo(() => {
|
||||
const installedAddons = stremioService.getInstalledAddons();
|
||||
const streams = type === 'series' ? episodeStreams : groupedStreams;
|
||||
const streams = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
|
||||
|
||||
// Make sure we include all providers with streams, not just those in availableProviders
|
||||
const allProviders = new Set([
|
||||
|
|
@ -1389,7 +1418,7 @@ export const StreamsScreen = () => {
|
|||
}, [availableProviders, type, episodeStreams, groupedStreams, settings.streamDisplayMode]);
|
||||
|
||||
const sections = useMemo(() => {
|
||||
const streams = type === 'series' ? episodeStreams : groupedStreams;
|
||||
const streams = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
|
||||
const installedAddons = stremioService.getInstalledAddons();
|
||||
|
||||
// Filter streams by selected provider
|
||||
|
|
@ -1681,8 +1710,8 @@ export const StreamsScreen = () => {
|
|||
});
|
||||
}, [episodeImage, bannerImage, metadata]);
|
||||
|
||||
const isLoading = type === 'series' ? loadingEpisodeStreams : loadingStreams;
|
||||
const streams = type === 'series' ? episodeStreams : groupedStreams;
|
||||
const isLoading = (type === 'series' || (type === 'other' && selectedEpisode)) ? loadingEpisodeStreams : loadingStreams;
|
||||
const streams = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
|
||||
|
||||
// Determine extended loading phases
|
||||
const streamsEmpty = Object.keys(streams).length === 0;
|
||||
|
|
@ -1747,7 +1776,7 @@ export const StreamsScreen = () => {
|
|||
>
|
||||
<MaterialIcons name="arrow-back" size={24} color={colors.white} />
|
||||
<Text style={styles.backButtonText}>
|
||||
{type === 'series' ? 'Back to Episodes' : 'Back to Info'}
|
||||
{(type === 'series' || (type === 'other' && selectedEpisode)) ? 'Back to Episodes' : 'Back to Info'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
|
@ -1772,7 +1801,7 @@ export const StreamsScreen = () => {
|
|||
</View>
|
||||
)}
|
||||
|
||||
{type === 'series' && (
|
||||
{(type === 'series' || (type === 'other' && selectedEpisode)) && (
|
||||
<View style={[styles.streamsHeroContainer]}>
|
||||
<View style={StyleSheet.absoluteFill}>
|
||||
<View
|
||||
|
|
@ -1972,9 +2001,9 @@ export const StreamsScreen = () => {
|
|||
showAlert={(t, m) => openAlert(t, m)}
|
||||
parentTitle={metadata?.name}
|
||||
parentType={type as 'movie' | 'series'}
|
||||
parentSeason={type === 'series' ? currentEpisode?.season_number : undefined}
|
||||
parentEpisode={type === 'series' ? currentEpisode?.episode_number : undefined}
|
||||
parentEpisodeTitle={type === 'series' ? currentEpisode?.name : undefined}
|
||||
parentSeason={(type === 'series' || type === 'other') ? currentEpisode?.season_number : undefined}
|
||||
parentEpisode={(type === 'series' || type === 'other') ? currentEpisode?.episode_number : undefined}
|
||||
parentEpisodeTitle={(type === 'series' || type === 'other') ? currentEpisode?.name : undefined}
|
||||
parentPosterUrl={episodeImage || metadata?.poster || undefined}
|
||||
providerName={streams && Object.keys(streams).find(pid => (streams as any)[pid]?.streams?.includes?.(item))}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -639,7 +639,8 @@ class StremioService {
|
|||
// Special handling for Cinemeta
|
||||
if (manifest.id === 'com.linvo.cinemeta') {
|
||||
const baseUrl = 'https://v3-cinemeta.strem.io';
|
||||
let url = `${baseUrl}/catalog/${type}/${id}.json`;
|
||||
const encodedId = encodeURIComponent(id);
|
||||
let url = `${baseUrl}/catalog/${type}/${encodedId}.json`;
|
||||
|
||||
// Add paging
|
||||
url += `?skip=${(page - 1) * this.DEFAULT_PAGE_SIZE}`;
|
||||
|
|
@ -670,9 +671,10 @@ class StremioService {
|
|||
|
||||
try {
|
||||
const { baseUrl, queryParams } = this.getAddonBaseURL(manifest.url);
|
||||
|
||||
|
||||
// Build the catalog URL
|
||||
let url = `${baseUrl}/catalog/${type}/${id}.json`;
|
||||
const encodedId = encodeURIComponent(id);
|
||||
let url = `${baseUrl}/catalog/${type}/${encodedId}.json`;
|
||||
|
||||
// Add paging
|
||||
url += `?skip=${(page - 1) * this.DEFAULT_PAGE_SIZE}`;
|
||||
|
|
@ -710,12 +712,15 @@ class StremioService {
|
|||
|
||||
// If a preferred addon is specified, try it first
|
||||
if (preferredAddonId) {
|
||||
logger.log(`🔍 [getMetaDetails] Looking for preferred addon: ${preferredAddonId}`);
|
||||
const preferredAddon = addons.find(addon => addon.id === preferredAddonId);
|
||||
logger.log(`🔍 [getMetaDetails] Found preferred addon: ${preferredAddon ? preferredAddon.id : 'null'}`);
|
||||
|
||||
if (preferredAddon && preferredAddon.resources) {
|
||||
// Build URL for metadata request
|
||||
const { baseUrl, queryParams } = this.getAddonBaseURL(preferredAddon.url || '');
|
||||
const url = queryParams ? `${baseUrl}/meta/${type}/${id}.json?${queryParams}` : `${baseUrl}/meta/${type}/${id}.json`;
|
||||
const encodedId = encodeURIComponent(id);
|
||||
const url = queryParams ? `${baseUrl}/meta/${type}/${encodedId}.json?${queryParams}` : `${baseUrl}/meta/${type}/${encodedId}.json`;
|
||||
|
||||
// Check if addon supports meta resource for this type
|
||||
let hasMetaSupport = false;
|
||||
|
|
@ -744,7 +749,8 @@ class StremioService {
|
|||
|
||||
if (hasMetaSupport) {
|
||||
try {
|
||||
|
||||
logger.log(`🔗 [${preferredAddon.name}] Requesting metadata: ${url} (preferred, id=${id}, type=${type})`);
|
||||
|
||||
const response = await this.retryRequest(async () => {
|
||||
return await axios.get(url, { timeout: 10000 });
|
||||
});
|
||||
|
|
@ -767,7 +773,8 @@ class StremioService {
|
|||
|
||||
for (const baseUrl of cinemetaUrls) {
|
||||
try {
|
||||
const url = `${baseUrl}/meta/${type}/${id}.json`;
|
||||
const encodedId = encodeURIComponent(id);
|
||||
const url = `${baseUrl}/meta/${type}/${encodedId}.json`;
|
||||
|
||||
const response = await this.retryRequest(async () => {
|
||||
return await axios.get(url, { timeout: 10000 });
|
||||
|
|
@ -786,8 +793,9 @@ class StremioService {
|
|||
for (const addon of addons) {
|
||||
if (!addon.resources || addon.id === 'com.linvo.cinemeta' || addon.id === preferredAddonId) continue;
|
||||
|
||||
// Check if addon supports meta resource for this type (handles both string and object formats)
|
||||
// Check if addon supports meta resource for this type AND idPrefix (handles both string and object formats)
|
||||
let hasMetaSupport = false;
|
||||
let supportsIdPrefix = false;
|
||||
|
||||
for (const resource of addon.resources) {
|
||||
// Check if the current element is a ResourceObject
|
||||
|
|
@ -797,6 +805,12 @@ class StremioService {
|
|||
Array.isArray(typedResource.types) &&
|
||||
typedResource.types.includes(type)) {
|
||||
hasMetaSupport = true;
|
||||
// Match idPrefixes if present; otherwise assume support
|
||||
if (Array.isArray(typedResource.idPrefixes) && typedResource.idPrefixes.length > 0) {
|
||||
supportsIdPrefix = typedResource.idPrefixes.some(p => id.startsWith(p));
|
||||
} else {
|
||||
supportsIdPrefix = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -804,18 +818,25 @@ class StremioService {
|
|||
else if (typeof resource === 'string' && resource === 'meta' && addon.types) {
|
||||
if (Array.isArray(addon.types) && addon.types.includes(type)) {
|
||||
hasMetaSupport = true;
|
||||
// For simple resources, check addon-level idPrefixes if present
|
||||
if (addon.idPrefixes && Array.isArray(addon.idPrefixes) && addon.idPrefixes.length > 0) {
|
||||
supportsIdPrefix = addon.idPrefixes.some(p => id.startsWith(p));
|
||||
} else {
|
||||
supportsIdPrefix = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasMetaSupport) continue;
|
||||
// Require both meta support and idPrefix compatibility
|
||||
if (!(hasMetaSupport && supportsIdPrefix)) continue;
|
||||
|
||||
try {
|
||||
const { baseUrl, queryParams } = this.getAddonBaseURL(addon.url || '');
|
||||
const url = queryParams ? `${baseUrl}/meta/${type}/${id}.json?${queryParams}` : `${baseUrl}/meta/${type}/${id}.json`;
|
||||
|
||||
logger.log(`🔗 [${addon.name}] Requesting metadata: ${url}`);
|
||||
const encodedId = encodeURIComponent(id);
|
||||
const url = queryParams ? `${baseUrl}/meta/${type}/${encodedId}.json?${queryParams}` : `${baseUrl}/meta/${type}/${encodedId}.json`;
|
||||
|
||||
logger.log(`🔗 [${addon.name}] Requesting metadata: ${url} (id=${id}, type=${type})`);
|
||||
const response = await this.retryRequest(async () => {
|
||||
return await axios.get(url, { timeout: 10000 });
|
||||
});
|
||||
|
|
@ -978,19 +999,17 @@ class StremioService {
|
|||
if (idType === 'imdb') {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
const tmdbIdNumber = await tmdbService.findTMDBIdByIMDB(baseId);
|
||||
|
||||
if (tmdbIdNumber) {
|
||||
tmdbId = tmdbIdNumber.toString();
|
||||
} else {
|
||||
return; // Skip local scrapers if we can't convert the ID
|
||||
logger.log('🔧 [getStreams] Skipping local scrapers: could not convert IMDb to TMDB for', baseId);
|
||||
}
|
||||
} else {
|
||||
} else if (idType === 'kitsu') {
|
||||
// For kitsu IDs, skip local scrapers as they don't support kitsu
|
||||
logger.log('🔧 [getStreams] Skipping local scrapers for kitsu ID:', baseId);
|
||||
// Don't return here - continue to Stremio addon processing
|
||||
}
|
||||
} catch (error) {
|
||||
return; // Skip local scrapers if ID parsing fails
|
||||
logger.warn('🔧 [getStreams] Skipping local scrapers due to ID parsing error:', error);
|
||||
}
|
||||
|
||||
// Execute local scrapers asynchronously with TMDB ID (only for IMDb IDs)
|
||||
|
|
@ -1006,6 +1025,8 @@ class StremioService {
|
|||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logger.log('🔧 [getStreams] Local scrapers not executed for this ID/type; continuing with Stremio addons');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1033,21 +1054,6 @@ class StremioService {
|
|||
let hasStreamResource = false;
|
||||
let supportsIdPrefix = false;
|
||||
|
||||
// Extract ID prefix from the ID
|
||||
let idPrefix = id.split(':')[0];
|
||||
|
||||
// For IMDb IDs (tt...), extract just the 'tt' prefix
|
||||
if (idPrefix.startsWith('tt')) {
|
||||
idPrefix = 'tt';
|
||||
}
|
||||
// For Kitsu IDs, keep the full prefix
|
||||
else if (idPrefix === 'kitsu') {
|
||||
idPrefix = 'kitsu';
|
||||
}
|
||||
// For other prefixes, keep as is
|
||||
|
||||
logger.log(`🔍 [getStreams] Checking if addon supports ID prefix: ${idPrefix} (from ${id.split(':')[0]})`);
|
||||
|
||||
// Iterate through the resources array, checking each element
|
||||
for (const resource of addon.resources) {
|
||||
// Check if the current element is a ResourceObject
|
||||
|
|
@ -1058,10 +1064,10 @@ class StremioService {
|
|||
typedResource.types.includes(type)) {
|
||||
hasStreamResource = true;
|
||||
|
||||
// Check if this addon supports the ID prefix
|
||||
if (Array.isArray(typedResource.idPrefixes)) {
|
||||
supportsIdPrefix = typedResource.idPrefixes.includes(idPrefix);
|
||||
logger.log(`🔍 [getStreams] Addon ${addon.id} supports prefixes: ${typedResource.idPrefixes.join(', ')}`);
|
||||
// Check if this addon supports the ID prefix (generic: any prefix that matches start of id)
|
||||
if (Array.isArray(typedResource.idPrefixes) && typedResource.idPrefixes.length > 0) {
|
||||
supportsIdPrefix = typedResource.idPrefixes.some(p => id.startsWith(p));
|
||||
logger.log(`🔍 [getStreams] Addon ${addon.id} supports prefixes: ${typedResource.idPrefixes.join(', ')} → matches=${supportsIdPrefix}`);
|
||||
} else {
|
||||
// If no idPrefixes specified, assume it supports all prefixes
|
||||
supportsIdPrefix = true;
|
||||
|
|
@ -1074,10 +1080,10 @@ class StremioService {
|
|||
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
|
||||
if (addon.idPrefixes && Array.isArray(addon.idPrefixes)) {
|
||||
supportsIdPrefix = addon.idPrefixes.includes(idPrefix);
|
||||
logger.log(`🔍 [getStreams] Addon ${addon.id} supports prefixes: ${addon.idPrefixes.join(', ')}`);
|
||||
// For simple string resources, check addon-level idPrefixes (generic)
|
||||
if (addon.idPrefixes && Array.isArray(addon.idPrefixes) && addon.idPrefixes.length > 0) {
|
||||
supportsIdPrefix = addon.idPrefixes.some(p => id.startsWith(p));
|
||||
logger.log(`🔍 [getStreams] Addon ${addon.id} supports prefixes: ${addon.idPrefixes.join(', ')} → matches=${supportsIdPrefix}`);
|
||||
} else {
|
||||
// If no idPrefixes specified, assume it supports all prefixes
|
||||
supportsIdPrefix = true;
|
||||
|
|
@ -1093,9 +1099,9 @@ class StremioService {
|
|||
if (!hasStreamResource) {
|
||||
logger.log(`❌ [getStreams] Addon ${addon.id} does not support streaming ${type}`);
|
||||
} else if (!supportsIdPrefix) {
|
||||
logger.log(`❌ [getStreams] Addon ${addon.id} supports ${type} but not ID prefix ${idPrefix}`);
|
||||
logger.log(`❌ [getStreams] Addon ${addon.id} supports ${type} but its idPrefixes did not match id=${id}`);
|
||||
} else {
|
||||
logger.log(`✅ [getStreams] Addon ${addon.id} supports streaming ${type} with prefix ${idPrefix}`);
|
||||
logger.log(`✅ [getStreams] Addon ${addon.id} supports streaming ${type} for id=${id}`);
|
||||
}
|
||||
|
||||
return canHandleRequest;
|
||||
|
|
@ -1122,7 +1128,8 @@ class StremioService {
|
|||
}
|
||||
|
||||
const { baseUrl, queryParams } = this.getAddonBaseURL(addon.url);
|
||||
const url = queryParams ? `${baseUrl}/stream/${type}/${id}.json?${queryParams}` : `${baseUrl}/stream/${type}/${id}.json`;
|
||||
const encodedId = encodeURIComponent(id);
|
||||
const url = queryParams ? `${baseUrl}/stream/${type}/${encodedId}.json?${queryParams}` : `${baseUrl}/stream/${type}/${encodedId}.json`;
|
||||
|
||||
logger.log(`🔗 [getStreams] Requesting streams from ${addon.name} (${addon.id}): ${url}`);
|
||||
|
||||
|
|
@ -1165,7 +1172,8 @@ class StremioService {
|
|||
}
|
||||
|
||||
const { baseUrl, queryParams } = this.getAddonBaseURL(addon.url);
|
||||
const streamPath = `/stream/${type}/${id}.json`;
|
||||
const encodedId = encodeURIComponent(id);
|
||||
const streamPath = `/stream/${type}/${encodedId}.json`;
|
||||
const url = queryParams ? `${baseUrl}${streamPath}?${queryParams}` : `${baseUrl}${streamPath}`;
|
||||
|
||||
logger.log(`Fetching streams from URL: ${url}`);
|
||||
|
|
@ -1363,10 +1371,11 @@ class StremioService {
|
|||
const { baseUrl } = this.getAddonBaseURL(addon.url || '');
|
||||
let url = '';
|
||||
if (type === 'series' && videoId) {
|
||||
const episodeInfo = videoId.replace('series:', '');
|
||||
const episodeInfo = encodeURIComponent(videoId.replace('series:', ''));
|
||||
url = `${baseUrl}/subtitles/series/${episodeInfo}.json`;
|
||||
} else {
|
||||
url = `${baseUrl}/subtitles/${type}/${id}.json`;
|
||||
const encodedId = encodeURIComponent(id);
|
||||
url = `${baseUrl}/subtitles/${type}/${encodedId}.json`;
|
||||
}
|
||||
logger.log(`Fetching subtitles from ${addon.name}: ${url}`);
|
||||
const response = await this.retryRequest(async () => axios.get(url, { timeout: 10000 }));
|
||||
|
|
|
|||
Loading…
Reference in a new issue