From b4b8648e25332735cb1c81500d2d3dff29948fc2 Mon Sep 17 00:00:00 2001 From: tapframe Date: Mon, 29 Dec 2025 01:03:06 +0530 Subject: [PATCH] embed stream fetch critical bug fix --- src/hooks/useMetadata.ts | 80 ++++++++++++++++++++----- src/screens/streams/useStreamsScreen.ts | 20 +++---- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/hooks/useMetadata.ts b/src/hooks/useMetadata.ts index b40428df..426d089a 100644 --- a/src/hooks/useMetadata.ts +++ b/src/hooks/useMetadata.ts @@ -1364,7 +1364,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat }; // Extract embedded streams from metadata videos (used by PPV-style addons) - const extractEmbeddedStreams = useCallback(() => { + const extractEmbeddedStreams = useCallback((episodeIdOverride?: string) => { if (!metadata?.videos) return; // Check if any video has embedded streams @@ -1378,11 +1378,11 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat const addonId = (metadata as any).addonId || 'embedded'; const addonName = (metadata as any).addonName || metadata.name || 'Embedded Streams'; - // Extract all streams from videos - const embeddedStreams: Stream[] = []; + // 1. Extract all streams for groupedStreams (legacy/movies behavior, or flat list) + const allEmbeddedStreams: Stream[] = []; for (const video of videosWithStreams) { for (const stream of video.streams) { - embeddedStreams.push({ + allEmbeddedStreams.push({ ...stream, name: stream.name || stream.title || video.title, title: stream.title || video.title, @@ -1392,15 +1392,15 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat } } - if (embeddedStreams.length > 0) { - if (__DEV__) console.log(`✅ [extractEmbeddedStreams] Found ${embeddedStreams.length} embedded streams from ${addonName}`); + if (allEmbeddedStreams.length > 0) { + if (__DEV__) console.log(`✅ [extractEmbeddedStreams] Found ${allEmbeddedStreams.length} embedded streams from ${addonName}`); // Add to grouped streams setGroupedStreams(prevStreams => ({ ...prevStreams, [addonId]: { addonName, - streams: embeddedStreams, + streams: allEmbeddedStreams, }, })); @@ -1412,10 +1412,44 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat return prevOrder; }); - // Mark loading as complete since we have streams - setLoadingStreams(false); + // If we are not waiting for episode streams, we can stop loading + if (!loadingEpisodeStreams) { + setLoadingStreams(false); + } } - }, [metadata]); + + // 2. Extract streams specifically for the selected episode + const episodeToUse = episodeIdOverride || selectedEpisode; + if (episodeToUse) { + const episodeVideo = videosWithStreams.find( + v => v.id === episodeToUse || + v.id === episodeToUse.split(':').pop() || // Handle cases where ID might have prefix + (v.season === 0 && v.episode === 1 && videosWithStreams.length === 1) // Single item PPV edge case + ); + + if (episodeVideo && episodeVideo.streams && episodeVideo.streams.length > 0) { + if (__DEV__) console.log(`✅ [extractEmbeddedStreams] Found embedded streams for episode ${episodeToUse}`); + + const episodeStreamsList: Stream[] = episodeVideo.streams.map((stream: any) => ({ + ...stream, + name: stream.name || stream.title || episodeVideo.title, + title: stream.title || episodeVideo.title, + addonId, + addonName, + })); + + setEpisodeStreams(prevStreams => ({ + ...prevStreams, + [addonId]: { + addonName, + streams: episodeStreamsList, + }, + })); + + setLoadingEpisodeStreams(false); + } + } + }, [metadata, selectedEpisode, loadingEpisodeStreams]); const loadStreams = async () => { const startTime = Date.now(); @@ -1822,11 +1856,24 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat if (__DEV__) console.log('✅ [loadEpisodeStreams] Converted to TMDB ID:', tmdbId); // Ensure consistent format - stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`; + // Ensure consistent format or fallback to episodeId if parsing failed + // This handles cases where 'tt' is used for a unique episode ID directly + if (!seasonNum && !episodeNum) { + stremioEpisodeId = episodeId; + } else { + stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`; + } if (__DEV__) console.log('🔧 [loadEpisodeStreams] Normalized episode ID for addons:', stremioEpisodeId); } else { tmdbId = id; - stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`; + // If season/episode parsing failed (empty strings), use the raw episode ID + // This handles custom IDs like "ppv-event-name" that don't follow "id:s:e" format + if (!seasonNum && !episodeNum) { + // Remove 'series:' prefix if present to be safe, though parsing logic above usually handles it + stremioEpisodeId = episodeId.replace(/^series:/, ''); + } else { + stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`; + } if (__DEV__) console.log('â„šī¸ [loadEpisodeStreams] Using ID as both TMDB and Stremio ID:', tmdbId); } @@ -1840,11 +1887,16 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat if (__DEV__) console.log('đŸŽŦ [loadEpisodeStreams] Using episode ID for Stremio addons:', stremioEpisodeId); // For collections, treat episodes as individual movies, not series - const contentType = isCollection ? 'movie' : 'series'; - if (__DEV__) console.log(`đŸŽŦ [loadEpisodeStreams] Using content type: ${contentType} for ${isCollection ? 'collection' : 'series'}`); + // For other types (e.g. StreamsPPV), preserve the original type unless it's explicitly 'series' logic we want + const contentType = isCollection ? 'movie' : type; + if (__DEV__) console.log(`đŸŽŦ [loadEpisodeStreams] Using content type: ${contentType} for ${isCollection ? 'collection' : type}`); processStremioSource(contentType, stremioEpisodeId, true); + // Also extract any embedded streams from metadata for this episode + // Also extract any embedded streams from metadata for this episode + extractEmbeddedStreams(episodeId); + // Monitor scraper completion status instead of using fixed timeout const checkEpisodeScrapersCompletion = () => { setScraperStatuses(currentStatuses => { diff --git a/src/screens/streams/useStreamsScreen.ts b/src/screens/streams/useStreamsScreen.ts index c1b05897..cbb6944e 100644 --- a/src/screens/streams/useStreamsScreen.ts +++ b/src/screens/streams/useStreamsScreen.ts @@ -357,7 +357,7 @@ export const useStreamsScreen = () => { async (stream: Stream, options?: { headers?: Record }) => { const finalHeaders = filterHeadersForVidrock(options?.headers || (stream.headers as any)); - const streamsToPass = type === 'series' || (type === 'other' && selectedEpisode) ? episodeStreams : groupedStreams; + const streamsToPass = selectedEpisode ? episodeStreams : groupedStreams; const streamName = stream.name || stream.title || 'Unnamed Stream'; const streamProvider = stream.addonId || stream.addonName || stream.name; @@ -545,8 +545,7 @@ export const useStreamsScreen = () => { useEffect(() => { if (!isMounted.current) return; - const currentStreamsData = - metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? episodeStreams : groupedStreams; + const currentStreamsData = selectedEpisode ? episodeStreams : groupedStreams; const providersWithStreams = Object.entries(currentStreamsData) .filter(([_, data]) => data.streams && data.streams.length > 0) @@ -592,8 +591,7 @@ export const useStreamsScreen = () => { const isSpecialFilter = selectedProvider === 'all' || selectedProvider === 'grouped-plugins'; if (isSpecialFilter) return; - const currentStreamsData = - metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? episodeStreams : groupedStreams; + const currentStreamsData = selectedEpisode ? episodeStreams : groupedStreams; const hasStreamsForProvider = currentStreamsData[selectedProvider] && currentStreamsData[selectedProvider].streams && @@ -675,8 +673,7 @@ export const useStreamsScreen = () => { // Autoplay effect useEffect(() => { if (settings.autoplayBestStream && !autoplayTriggered && isAutoplayWaiting) { - const streams = - metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? episodeStreams : groupedStreams; + const streams = selectedEpisode ? episodeStreams : groupedStreams; if (Object.keys(streams).length > 0) { const bestStream = getBestStream(streams); @@ -716,7 +713,7 @@ export const useStreamsScreen = () => { // Filter items for provider selector const filterItems = useMemo((): FilterItem[] => { const installedAddons = stremioService.getInstalledAddons(); - const streams = metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? episodeStreams : groupedStreams; + const streams = selectedEpisode ? episodeStreams : groupedStreams; const providersWithStreams = Object.keys(streams).filter(key => { const providerData = streams[key]; @@ -787,7 +784,7 @@ export const useStreamsScreen = () => { // Sections for stream list const sections = useMemo((): StreamSection[] => { - const streams = metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? episodeStreams : groupedStreams; + const streams = selectedEpisode ? episodeStreams : groupedStreams; const installedAddons = stremioService.getInstalledAddons(); const filteredEntries = Object.entries(streams).filter(([addonId]) => { @@ -1014,8 +1011,9 @@ export const useStreamsScreen = () => { const gradientColors = useMemo(() => createGradientColors(dominantColor), [dominantColor, createGradientColors]); // Loading states - const isLoading = metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? loadingEpisodeStreams : loadingStreams; - const streams = metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? episodeStreams : groupedStreams; + // Loading states + const isLoading = selectedEpisode ? loadingEpisodeStreams : loadingStreams; + const streams = selectedEpisode ? episodeStreams : groupedStreams; const streamsEmpty = Object.keys(streams).length === 0 ||