improved stream fetching logic

This commit is contained in:
tapframe 2025-12-27 21:13:11 +05:30
parent 7a5ecd3009
commit 91af3a4021
5 changed files with 591 additions and 544 deletions

@ -1 +1 @@
Subproject commit db3b10e64353349d0d72619ca7d779829e36fe4d
Subproject commit 1a94da42094b524b94a28902ae43c27e3286460d

@ -1 +1 @@
Subproject commit 118cd1ed3d498265e44230e5dbb015bdd59f9dad
Subproject commit a31e9a0d270066deb41fe330ed34ddeb0e38f0ab

File diff suppressed because it is too large Load diff

View file

@ -1569,6 +1569,11 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setScraperStatuses(initialStatuses);
setActiveFetchingScrapers(initialActiveFetching);
console.log('🔍 [loadStreams] Initialized activeFetchingScrapers:', initialActiveFetching);
// If no scrapers are available, stop loading immediately
if (initialStatuses.length === 0) {
setLoadingStreams(false);
}
} catch (error) {
if (__DEV__) console.error('Failed to initialize scraper tracking:', error);
}
@ -1701,6 +1706,11 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
setScraperStatuses(initialStatuses);
setActiveFetchingScrapers(initialActiveFetching);
console.log('🔍 [loadEpisodeStreams] Initialized activeFetchingScrapers:', initialActiveFetching);
// If no scrapers are available, stop loading immediately
if (initialStatuses.length === 0) {
setLoadingEpisodeStreams(false);
}
} catch (error) {
if (__DEV__) console.error('Failed to initialize episode scraper tracking:', error);
}
@ -1715,6 +1725,37 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const { isCollection: detectedCollection, addon: collectionAddon } = stremioService.isCollectionContent(id);
isCollection = detectedCollection;
// Parse season and episode numbers robustly
let showIdStr = id;
let seasonNum = '';
let episodeNum = '';
try {
// Handle various episode ID formats
// 1. Internal format: "series:showId:season:episode"
// 2. Stremio/IMDb format: "tt12345:1:1"
// 3. TMDB format: "tmdb:123:1:1"
const cleanEpisodeId = episodeId.replace(/^series:/, '');
const parts = cleanEpisodeId.split(':');
if (parts.length >= 3) {
episodeNum = parts.pop() || '';
seasonNum = parts.pop() || '';
showIdStr = parts.join(':');
} else if (parts.length === 2) {
// Edge case: maybe just id:episode? unlikely but safe fallback
episodeNum = parts[1];
seasonNum = '1'; // Default
showIdStr = parts[0];
}
if (__DEV__) console.log(`🔍 [loadEpisodeStreams] Parsed ID: show=${showIdStr}, s=${seasonNum}, e=${episodeNum}`);
} catch (e) {
if (__DEV__) console.warn('⚠️ [loadEpisodeStreams] Failed to parse episode ID:', episodeId);
}
if (isCollection && collectionAddon) {
if (__DEV__) console.log(`🎬 [loadEpisodeStreams] Detected collection from addon: ${collectionAddon.name}, treating episodes as individual movies`);
@ -1728,9 +1769,15 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
stremioEpisodeId = episodeId; // Use the IMDb ID directly for Stremio addons
if (__DEV__) console.log('✅ [loadEpisodeStreams] Collection movie - using IMDb ID:', episodeId, 'TMDB ID:', tmdbId);
} else {
// Fallback: try to parse as TMDB ID
tmdbId = episodeId;
stremioEpisodeId = episodeId;
// Fallback: try to verify if it's a tmdb id
const isTmdb = episodeId.startsWith('tmdb:') || !isNaN(Number(episodeId));
if (isTmdb) {
const cleanId = episodeId.replace('tmdb:', '');
tmdbId = cleanId;
stremioEpisodeId = episodeId;
} else {
stremioEpisodeId = episodeId;
}
if (__DEV__) console.log('⚠️ [loadEpisodeStreams] Collection movie - using episodeId as-is:', episodeId);
}
} else if (id.startsWith('tmdb:')) {
@ -1739,13 +1786,11 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
// Try to get IMDb ID from metadata first, then convert if needed
if (metadata?.imdb_id) {
// Replace the series ID in episodeId with the IMDb ID
const [, season, episode] = episodeId.split(':');
stremioEpisodeId = `${metadata.imdb_id}:${season}:${episode}`;
// Use format: imdb_id:season:episode
stremioEpisodeId = `${metadata.imdb_id}:${seasonNum}:${episodeNum}`;
if (__DEV__) console.log('✅ [loadEpisodeStreams] Using IMDb ID from metadata for Stremio episode:', stremioEpisodeId);
} else if (imdbId) {
const [, season, episode] = episodeId.split(':');
stremioEpisodeId = `${imdbId}:${season}:${episode}`;
stremioEpisodeId = `${imdbId}:${seasonNum}:${episodeNum}`;
if (__DEV__) console.log('✅ [loadEpisodeStreams] Using stored IMDb ID for Stremio episode:', stremioEpisodeId);
} else {
// Convert TMDB ID to IMDb ID for Stremio addons
@ -1753,14 +1798,17 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const externalIds = await withTimeout(tmdbService.getShowExternalIds(parseInt(tmdbId)), API_TIMEOUT);
if (externalIds?.imdb_id) {
const [, season, episode] = episodeId.split(':');
stremioEpisodeId = `${externalIds.imdb_id}:${season}:${episode}`;
stremioEpisodeId = `${externalIds.imdb_id}:${seasonNum}:${episodeNum}`;
if (__DEV__) console.log('✅ [loadEpisodeStreams] Converted TMDB to IMDb ID for Stremio episode:', stremioEpisodeId);
} else {
if (__DEV__) console.log('⚠️ [loadEpisodeStreams] No IMDb ID found for TMDB ID, using original episode ID:', stremioEpisodeId);
// Fallback to TMDB format if conversions fail
// e.g. tmdb:123:1:1
stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`;
if (__DEV__) console.log('⚠️ [loadEpisodeStreams] No IMDb ID found for TMDB ID, using TMDB episode ID:', stremioEpisodeId);
}
} catch (error) {
if (__DEV__) console.log('⚠️ [loadEpisodeStreams] Failed to convert TMDB to IMDb, using original episode ID:', error);
stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`;
if (__DEV__) console.log('⚠️ [loadEpisodeStreams] Failed to convert TMDB to IMDb, using TMDB episode ID:', error);
}
}
} else if (id.startsWith('tt')) {
@ -1772,20 +1820,18 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
if (__DEV__) console.log('📝 [loadEpisodeStreams] TMDB enrichment disabled, skipping IMDB to TMDB conversion');
}
if (__DEV__) console.log('✅ [loadEpisodeStreams] Converted to TMDB ID:', tmdbId);
// Normalize episode id to 'tt:season:episode' format for addons that expect tt prefix
const parts = episodeId.split(':');
if (parts.length === 3 && parts[0] === 'series') {
stremioEpisodeId = `${id}:${parts[1]}:${parts[2]}`;
if (__DEV__) console.log('🔧 [loadEpisodeStreams] Normalized episode ID for addons:', stremioEpisodeId);
}
// Ensure consistent format
stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`;
if (__DEV__) console.log('🔧 [loadEpisodeStreams] Normalized episode ID for addons:', stremioEpisodeId);
} else {
tmdbId = id;
stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`;
if (__DEV__) console.log(' [loadEpisodeStreams] Using ID as both TMDB and Stremio ID:', tmdbId);
}
// Extract episode info from the episodeId for logging
const [, season, episode] = episodeId.split(':');
const episodeQuery = `?s=${season}&e=${episode}`;
const episodeQuery = `?s=${seasonNum}&e=${episodeNum}`;
if (__DEV__) console.log(` [loadEpisodeStreams] Episode query: ${episodeQuery}`);
if (__DEV__) console.log('🔄 [loadEpisodeStreams] Starting stream requests');

View file

@ -57,7 +57,7 @@ export const useStreamsScreen = () => {
// Dimension tracking
const [dimensions, setDimensions] = useState(Dimensions.get('window'));
const prevDimensionsRef = useRef({ width: dimensions.width, height: dimensions.height });
const deviceWidth = dimensions.width;
const isTablet = useMemo(() => deviceWidth >= TABLET_BREAKPOINT, [deviceWidth]);
@ -119,7 +119,7 @@ export const useStreamsScreen = () => {
} = useMetadata({ id, type });
// Get banner image
const setMetadataStub = useCallback(() => {}, []);
const setMetadataStub = useCallback(() => { }, []);
const memoizedSettings = useMemo(
() => settings,
[settings.logoSourcePreference, settings.tmdbLanguagePreference, settings.enrichMetadataWithTMDB]
@ -183,7 +183,7 @@ export const useStreamsScreen = () => {
try {
setAlertTitle(title);
setAlertMessage(message);
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => {} }]);
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => { } }]);
setAlertVisible(true);
} catch (error) {
console.warn('[StreamsScreen] Error showing alert:', error);
@ -390,7 +390,7 @@ export const useStreamsScreen = () => {
if (!videoType && /xprime/i.test(providerId)) {
videoType = 'm3u8';
}
} catch {}
} catch { }
const playerRoute = Platform.OS === 'ios' ? 'PlayerIOS' : 'PlayerAndroid';
@ -438,7 +438,7 @@ export const useStreamsScreen = () => {
/format=mkv\b/i.test(lowerUrl) ||
/container=mkv\b/i.test(lowerUrl);
const isHttp = lowerUrl.startsWith('http://') || lowerUrl.startsWith('https://');
if (!isMkvByPath && isHttp) {
try {
const mkvDetected = await Promise.race<boolean>([
@ -609,14 +609,14 @@ export const useStreamsScreen = () => {
useEffect(() => {
// Build a unique key for the current content
const currentKey = `${id}:${type}:${episodeId || ''}`;
// Reset refs if content changed
if (lastLoadedIdRef.current !== currentKey) {
hasDoneInitialLoadRef.current = false;
isLoadingStreamsRef.current = false;
lastLoadedIdRef.current = currentKey;
}
// Only proceed if we haven't done the initial load for this content
if (hasDoneInitialLoadRef.current) return;
@ -803,18 +803,18 @@ export const useStreamsScreen = () => {
const sortedEntries = filteredEntries.sort(([addonIdA], [addonIdB]) => {
const isAddonA = installedAddons.some(addon => addon.id === addonIdA);
const isAddonB = installedAddons.some(addon => addon.id === addonIdB);
// Addons always come before plugins
if (isAddonA && !isAddonB) return -1;
if (!isAddonA && isAddonB) return 1;
// Both are addons - sort by installation order
if (isAddonA && isAddonB) {
const indexA = installedAddons.findIndex(addon => addon.id === addonIdA);
const indexB = installedAddons.findIndex(addon => addon.id === addonIdB);
return indexA - indexB;
}
// Both are plugins - sort by response order
const responseIndexA = addonResponseOrder.indexOf(addonIdA);
const responseIndexB = addonResponseOrder.indexOf(addonIdB);
@ -1021,8 +1021,9 @@ export const useStreamsScreen = () => {
Object.keys(streams).length === 0 ||
Object.values(streams).every(provider => !provider.streams || provider.streams.length === 0);
const loadElapsed = streamsLoadStart ? Date.now() - streamsLoadStart : 0;
const showInitialLoading = streamsEmpty && (streamsLoadStart === null || loadElapsed < 10000);
const showStillFetching = streamsEmpty && loadElapsed >= 10000;
const isActuallyLoading = isLoading || activeFetchingScrapers.length > 0;
const showInitialLoading = streamsEmpty && isActuallyLoading && (streamsLoadStart === null || loadElapsed < 10000);
const showStillFetching = streamsEmpty && isActuallyLoading && loadElapsed >= 10000;
return {
// Route params
@ -1031,19 +1032,19 @@ export const useStreamsScreen = () => {
episodeId,
episodeThumbnail,
fromPlayer,
// Theme
currentTheme,
colors,
settings,
// Navigation
navigation,
handleBack,
// Tablet
isTablet,
// Alert
alertVisible,
alertTitle,
@ -1051,14 +1052,14 @@ export const useStreamsScreen = () => {
alertActions,
openAlert,
closeAlert,
// Metadata
metadata,
imdbId,
bannerImage,
currentEpisode,
groupedEpisodes,
// Streams
streams,
groupedStreams,
@ -1068,7 +1069,7 @@ export const useStreamsScreen = () => {
selectedProvider,
handleProviderChange,
handleStreamPress,
// Loading states
isLoading,
loadingStreams,
@ -1079,19 +1080,19 @@ export const useStreamsScreen = () => {
showStillFetching,
showNoSourcesError,
hasStremioStreamProviders,
// Autoplay
isAutoplayWaiting,
autoplayTriggered,
// Scrapers
activeFetchingScrapers,
scraperLogos,
// Movie
movieLogoError,
setMovieLogoError,
// Episode
episodeImage,
effectiveEpisodeVote,
@ -1099,7 +1100,7 @@ export const useStreamsScreen = () => {
hasIMDbRating,
tmdbEpisodeOverride,
selectedEpisode,
// Backdrop
mobileBackdropSource,
gradientColors,