From d88962ae01740c5701f3fe7a1f189e0dc99465cd Mon Sep 17 00:00:00 2001 From: tapframe Date: Fri, 20 Jun 2025 13:00:24 +0530 Subject: [PATCH] Enhance metadata handling and navigation with addon support This update introduces support for addon IDs in various components, including CatalogSection, ContinueWatchingSection, and MetadataScreen, allowing for enhanced metadata fetching. The CatalogService now includes methods for retrieving both basic and enhanced content details based on the specified addon. Additionally, improvements to the loading process in HomeScreen ensure a more efficient catalog loading experience. These changes enhance user experience by providing richer content details and smoother navigation. --- src/components/home/CatalogSection.tsx | 2 +- .../home/ContinueWatchingSection.tsx | 4 +- src/components/metadata/MovieContent.tsx | 6 +- src/hooks/useMetadata.ts | 88 ++++++++++-- src/navigation/AppNavigator.tsx | 1 + src/screens/CatalogScreen.tsx | 2 +- src/screens/HomeScreen.tsx | 14 +- src/screens/MetadataScreen.tsx | 6 +- src/services/catalogService.ts | 128 ++++++++++++++++- src/services/stremioService.ts | 135 ++++++++++++++++-- src/types/metadata.ts | 21 ++- src/types/navigation.d.ts | 1 + 12 files changed, 365 insertions(+), 43 deletions(-) diff --git a/src/components/home/CatalogSection.tsx b/src/components/home/CatalogSection.tsx index 80a3bdc2..1a2987d3 100644 --- a/src/components/home/CatalogSection.tsx +++ b/src/components/home/CatalogSection.tsx @@ -60,7 +60,7 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => { const { currentTheme } = useTheme(); const handleContentPress = (id: string, type: string) => { - navigation.navigate('Metadata', { id, type }); + navigation.navigate('Metadata', { id, type, addonId: catalog.addon }); }; const renderContentItem = ({ item, index }: { item: StreamingContent, index: number }) => { diff --git a/src/components/home/ContinueWatchingSection.tsx b/src/components/home/ContinueWatchingSection.tsx index 13c420dc..ba9a8ce4 100644 --- a/src/components/home/ContinueWatchingSection.tsx +++ b/src/components/home/ContinueWatchingSection.tsx @@ -116,8 +116,8 @@ const ContinueWatchingSection = React.forwardRef((props, re let content: StreamingContent | null = null; - // Get content details using catalogService - content = await catalogService.getContentDetails(type, id); + // Get basic content details using catalogService (no enhanced metadata needed for continue watching) + content = await catalogService.getBasicContentDetails(type, id); if (content) { // Extract season and episode info from episodeId if available diff --git a/src/components/metadata/MovieContent.tsx b/src/components/metadata/MovieContent.tsx index 95836e13..eceabe31 100644 --- a/src/components/metadata/MovieContent.tsx +++ b/src/components/metadata/MovieContent.tsx @@ -10,7 +10,7 @@ interface MovieContentProps { export const MovieContent: React.FC = ({ metadata }) => { const { currentTheme } = useTheme(); const hasCast = Array.isArray(metadata.cast) && metadata.cast.length > 0; - const castDisplay = hasCast ? (metadata.cast as string[]).slice(0, 5).join(', ') : ''; + const castDisplay = hasCast ? metadata.cast!.slice(0, 5).join(', ') : ''; return ( @@ -23,10 +23,10 @@ export const MovieContent: React.FC = ({ metadata }) => { )} - {metadata.writer && ( + {metadata.writer && metadata.writer.length > 0 && ( Writer: - {metadata.writer} + {Array.isArray(metadata.writer) ? metadata.writer.join(', ') : metadata.writer} )} diff --git a/src/hooks/useMetadata.ts b/src/hooks/useMetadata.ts index 5a44301a..4e48ddef 100644 --- a/src/hooks/useMetadata.ts +++ b/src/hooks/useMetadata.ts @@ -60,6 +60,7 @@ const withRetry = async ( interface UseMetadataProps { id: string; type: string; + addonId?: string; } interface UseMetadataReturn { @@ -94,7 +95,7 @@ interface UseMetadataReturn { imdbId: string | null; } -export const useMetadata = ({ id, type }: UseMetadataProps): UseMetadataReturn => { +export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadataReturn => { const [metadata, setMetadata] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -411,7 +412,7 @@ export const useMetadata = ({ id, type }: UseMetadataProps): UseMetadataReturn = if (writers.length > 0) { (formattedMovie as any).creators = writers; - (formattedMovie as StreamingContent & { writer: string }).writer = writers.join(', '); + (formattedMovie as any).writer = writers; } } } catch (error) { @@ -513,10 +514,10 @@ export const useMetadata = ({ id, type }: UseMetadataProps): UseMetadataReturn = const [content, castData] = await Promise.allSettled([ // Load content with timeout and retry withRetry(async () => { - const result = await withTimeout( - catalogService.getContentDetails(type, actualId), - API_TIMEOUT - ); + const result = await withTimeout( + catalogService.getEnhancedContentDetails(type, actualId, addonId), + API_TIMEOUT + ); // Store the actual ID used (could be IMDB) if (actualId.startsWith('tt')) { setImdbId(actualId); @@ -540,8 +541,10 @@ export const useMetadata = ({ id, type }: UseMetadataProps): UseMetadataReturn = cacheService.setMetadata(id, type, content.value); if (type === 'series') { - // Load series data in parallel with other data - loadSeriesData().catch(console.error); + // Load series data after the enhanced metadata is processed + setTimeout(() => { + loadSeriesData().catch(console.error); + }, 100); } } else { throw new Error('Content not found'); @@ -564,6 +567,67 @@ export const useMetadata = ({ id, type }: UseMetadataProps): UseMetadataReturn = const loadSeriesData = async () => { setLoadingSeasons(true); try { + // First check if we have episode data from the addon + const addonVideos = metadata?.videos; + if (addonVideos && Array.isArray(addonVideos) && addonVideos.length > 0) { + logger.log(`🎬 Found ${addonVideos.length} episodes from addon metadata for ${metadata?.name || id}`); + + // Group addon episodes by season + const groupedAddonEpisodes: GroupedEpisodes = {}; + + addonVideos.forEach((video: any) => { + const seasonNumber = video.season || 1; + const episodeNumber = video.episode || video.number || 1; + + if (!groupedAddonEpisodes[seasonNumber]) { + groupedAddonEpisodes[seasonNumber] = []; + } + + // Convert addon episode format to our Episode interface + const episode: Episode = { + id: video.id, + name: video.name || video.title || `Episode ${episodeNumber}`, + overview: video.overview || video.description || '', + season_number: seasonNumber, + episode_number: episodeNumber, + air_date: video.released ? video.released.split('T')[0] : video.firstAired ? video.firstAired.split('T')[0] : '', + still_path: video.thumbnail ? video.thumbnail.replace('https://image.tmdb.org/t/p/w500', '') : null, + vote_average: parseFloat(video.rating) || 0, + runtime: undefined, + episodeString: `S${seasonNumber.toString().padStart(2, '0')}E${episodeNumber.toString().padStart(2, '0')}`, + stremioId: video.id, + season_poster_path: null + }; + + groupedAddonEpisodes[seasonNumber].push(episode); + }); + + // Sort episodes within each season + Object.keys(groupedAddonEpisodes).forEach(season => { + groupedAddonEpisodes[parseInt(season)].sort((a, b) => a.episode_number - b.episode_number); + }); + + logger.log(`📺 Processed addon episodes into ${Object.keys(groupedAddonEpisodes).length} seasons`); + setGroupedEpisodes(groupedAddonEpisodes); + + // Set the first available season + const seasons = Object.keys(groupedAddonEpisodes).map(Number); + const firstSeason = Math.min(...seasons); + logger.log(`📺 Setting season ${firstSeason} as selected (${groupedAddonEpisodes[firstSeason]?.length || 0} episodes)`); + setSelectedSeason(firstSeason); + setEpisodes(groupedAddonEpisodes[firstSeason] || []); + + // Try to get TMDB ID for additional metadata (cast, etc.) but don't override episodes + const tmdbIdResult = await tmdbService.findTMDBIdByIMDB(id); + if (tmdbIdResult) { + setTmdbId(tmdbIdResult); + } + + return; // Use addon episodes, skip TMDB loading + } + + // Fallback to TMDB if no addon episodes + logger.log('📺 No addon episodes found, falling back to TMDB'); const tmdbIdResult = await tmdbService.findTMDBIdByIMDB(id); if (tmdbIdResult) { setTmdbId(tmdbIdResult); @@ -866,6 +930,14 @@ export const useMetadata = ({ id, type }: UseMetadataProps): UseMetadataReturn = loadMetadata(); }, [id, type]); + // Re-run series data loading when metadata updates with videos + useEffect(() => { + if (metadata && type === 'series' && metadata.videos && metadata.videos.length > 0) { + logger.log(`🎬 Metadata updated with ${metadata.videos.length} episodes, reloading series data`); + loadSeriesData().catch(console.error); + } + }, [metadata?.videos, type]); + const loadRecommendations = useCallback(async () => { if (!tmdbId) return; diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index ee73cb26..1c1cec2c 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -54,6 +54,7 @@ export type RootStackParamList = { id: string; type: string; episodeId?: string; + addonId?: string; }; Streams: { id: string; diff --git a/src/screens/CatalogScreen.tsx b/src/screens/CatalogScreen.tsx index 8401e791..e80afcc2 100644 --- a/src/screens/CatalogScreen.tsx +++ b/src/screens/CatalogScreen.tsx @@ -503,7 +503,7 @@ const CatalogScreen: React.FC = ({ route, navigation }) => { width: NUM_COLUMNS === 2 ? ITEM_WIDTH : ITEM_WIDTH } ]} - onPress={() => navigation.navigate('Metadata', { id: item.id, type: item.type })} + onPress={() => navigation.navigate('Metadata', { id: item.id, type: item.type, addonId })} activeOpacity={0.7} > { // Initialize catalogs array with proper length setCatalogs(new Array(catalogIndex).fill(null)); - // Wait for all catalogs to finish loading (success or failure) - await Promise.allSettled(catalogPromises); - console.log('[HomeScreen] All catalogs processed'); - - // Filter out null values to get only successfully loaded catalogs - setCatalogs(prevCatalogs => prevCatalogs.filter(catalog => catalog !== null)); + // Start all catalog loading promises but don't wait for them + // They will update the state progressively as they complete + Promise.allSettled(catalogPromises).then(() => { + console.log('[HomeScreen] All catalogs processed'); + + // Final cleanup: Filter out null values to get only successfully loaded catalogs + setCatalogs(prevCatalogs => prevCatalogs.filter(catalog => catalog !== null)); + }); } catch (error) { console.error('[HomeScreen] Error in progressive catalog loading:', error); diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx index 73b5d5b3..325f0036 100644 --- a/src/screens/MetadataScreen.tsx +++ b/src/screens/MetadataScreen.tsx @@ -45,9 +45,9 @@ import { TraktService, TraktPlaybackItem } from '../services/traktService'; const { height } = Dimensions.get('window'); const MetadataScreen: React.FC = () => { - const route = useRoute, string>>(); + const route = useRoute, string>>(); const navigation = useNavigation>(); - const { id, type, episodeId } = route.params; + const { id, type, episodeId, addonId } = route.params; // Consolidated hooks for better performance const { settings } = useSettings(); @@ -78,7 +78,7 @@ const MetadataScreen: React.FC = () => { loadingRecommendations, setMetadata, imdbId, - } = useMetadata({ id, type }); + } = useMetadata({ id, type, addonId }); // Optimized hooks with memoization const watchProgressData = useWatchProgress(id, type as 'movie' | 'series', episodeId, episodes); diff --git a/src/services/catalogService.ts b/src/services/catalogService.ts index 6c078463..d8ede4b2 100644 --- a/src/services/catalogService.ts +++ b/src/services/catalogService.ts @@ -54,6 +54,22 @@ export interface StreamingContent { directors?: string[]; creators?: string[]; certification?: string; + // Enhanced metadata from addons + country?: string; + writer?: string[]; + links?: Array<{ + name: string; + category: string; + url: string; + }>; + behaviorHints?: { + defaultVideoId?: string; + hasScheduledVideos?: boolean; + [key: string]: any; + }; + imdb_id?: string; + slug?: string; + releaseInfo?: string; } export interface CatalogContent { @@ -442,7 +458,7 @@ class CatalogService { } } - async getContentDetails(type: string, id: string): Promise { + async getContentDetails(type: string, id: string, preferredAddonId?: string): Promise { try { // Try up to 3 times with increasing delays let meta = null; @@ -450,7 +466,7 @@ class CatalogService { for (let i = 0; i < 3; i++) { try { - meta = await stremioService.getMetaDetails(type, id); + meta = await stremioService.getMetaDetails(type, id, preferredAddonId); if (meta) break; await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i))); } catch (error) { @@ -461,8 +477,8 @@ class CatalogService { } if (meta) { - // Add to recent content - const content = this.convertMetaToStreamingContent(meta); + // Add to recent content using enhanced conversion for full metadata + const content = this.convertMetaToStreamingContentEnhanced(meta); this.addToRecentContent(content); // Check if it's in the library @@ -482,7 +498,54 @@ class CatalogService { } } + // Public method for getting enhanced metadata details (used by MetadataScreen) + async getEnhancedContentDetails(type: string, id: string, preferredAddonId?: string): Promise { + logger.log(`🔍 [MetadataScreen] Fetching enhanced metadata for ${type}:${id} ${preferredAddonId ? `from addon ${preferredAddonId}` : ''}`); + return this.getContentDetails(type, id, preferredAddonId); + } + + // Public method for getting basic content details without enhanced processing (used by ContinueWatching, etc.) + async getBasicContentDetails(type: string, id: string, preferredAddonId?: string): Promise { + try { + // Try up to 3 times with increasing delays + let meta = null; + let lastError = null; + + for (let i = 0; i < 3; i++) { + try { + meta = await stremioService.getMetaDetails(type, id, preferredAddonId); + if (meta) break; + await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i))); + } catch (error) { + lastError = error; + logger.error(`Attempt ${i + 1} failed to get basic content details for ${type}:${id}:`, error); + await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i))); + } + } + + if (meta) { + // Use basic conversion without enhanced metadata processing + const content = this.convertMetaToStreamingContent(meta); + + // Check if it's in the library + content.inLibrary = this.library[`${type}:${id}`] !== undefined; + + return content; + } + + if (lastError) { + throw lastError; + } + + return null; + } catch (error) { + logger.error(`Failed to get basic content details for ${type}:${id}:`, error); + return null; + } + } + private convertMetaToStreamingContent(meta: Meta): StreamingContent { + // Basic conversion for catalog display - no enhanced metadata processing return { id: meta.id, type: meta.type, @@ -490,17 +553,70 @@ class CatalogService { poster: meta.poster || 'https://via.placeholder.com/300x450/cccccc/666666?text=No+Image', posterShape: 'poster', banner: meta.background, - logo: `https://images.metahub.space/logo/medium/${meta.id}/img`, + logo: `https://images.metahub.space/logo/medium/${meta.id}/img`, // Use metahub for catalog display imdbRating: meta.imdbRating, year: meta.year, genres: meta.genres, description: meta.description, runtime: meta.runtime, inLibrary: this.library[`${meta.type}:${meta.id}`] !== undefined, - certification: meta.certification + certification: meta.certification, + releaseInfo: meta.releaseInfo, }; } + // Enhanced conversion for detailed metadata (used only when fetching individual content details) + private convertMetaToStreamingContentEnhanced(meta: Meta): StreamingContent { + // Enhanced conversion to utilize all available metadata from addons + const converted: StreamingContent = { + id: meta.id, + type: meta.type, + name: meta.name, + poster: meta.poster || 'https://via.placeholder.com/300x450/cccccc/666666?text=No+Image', + posterShape: 'poster', + banner: meta.background, + // Use addon's logo if available, fallback to metahub + logo: (meta as any).logo || `https://images.metahub.space/logo/medium/${meta.id}/img`, + imdbRating: meta.imdbRating, + year: meta.year, + genres: meta.genres, + description: meta.description, + runtime: meta.runtime, + inLibrary: this.library[`${meta.type}:${meta.id}`] !== undefined, + certification: meta.certification, + // Enhanced fields from addon metadata + directors: (meta as any).director ? + (Array.isArray((meta as any).director) ? (meta as any).director : [(meta as any).director]) + : undefined, + writer: (meta as any).writer || undefined, + country: (meta as any).country || undefined, + imdb_id: (meta as any).imdb_id || undefined, + slug: (meta as any).slug || undefined, + releaseInfo: meta.releaseInfo || (meta as any).releaseInfo || undefined, + trailerStreams: (meta as any).trailerStreams || undefined, + links: (meta as any).links || undefined, + behaviorHints: (meta as any).behaviorHints || undefined, + }; + + // Cast is handled separately by the dedicated CastSection component via TMDB + + // Log if rich metadata is found + if ((meta as any).trailerStreams?.length > 0) { + logger.log(`🎬 Enhanced metadata: Found ${(meta as any).trailerStreams.length} trailers for ${meta.name}`); + } + + if ((meta as any).links?.length > 0) { + logger.log(`🔗 Enhanced metadata: Found ${(meta as any).links.length} links for ${meta.name}`); + } + + // Handle videos/episodes if available + if ((meta as any).videos) { + converted.videos = (meta as any).videos; + } + + return converted; + } + private notifyLibrarySubscribers(): void { const items = Object.values(this.library); this.librarySubscribers.forEach(callback => callback(items)); diff --git a/src/services/stremioService.ts b/src/services/stremioService.ts index c1163862..7aef7993 100644 --- a/src/services/stremioService.ts +++ b/src/services/stremioService.ts @@ -26,9 +26,35 @@ export interface Meta { genres?: string[]; runtime?: string; cast?: string[]; - director?: string; - writer?: string; + director?: string | string[]; + writer?: string | string[]; certification?: string; + // Extended fields available from some addons + country?: string; + imdb_id?: string; + slug?: string; + released?: string; + trailerStreams?: Array<{ + title: string; + ytId: string; + }>; + links?: Array<{ + name: string; + category: string; + url: string; + }>; + behaviorHints?: { + defaultVideoId?: string; + hasScheduledVideos?: boolean; + [key: string]: any; + }; + app_extras?: { + cast?: Array<{ + name: string; + character?: string; + photo?: string; + }>; + }; } export interface Subtitle { @@ -464,8 +490,71 @@ class StremioService { } } - async getMetaDetails(type: string, id: string): Promise { + async getMetaDetails(type: string, id: string, preferredAddonId?: string): Promise { try { + const addons = this.getInstalledAddons(); + + // If a preferred addon is specified, try it first + if (preferredAddonId) { + logger.log(`🎯 Trying preferred addon first: ${preferredAddonId}`); + const preferredAddon = addons.find(addon => addon.id === preferredAddonId); + + if (preferredAddon && preferredAddon.resources) { + // Log what URL would be used for debugging + const { baseUrl, queryParams } = this.getAddonBaseURL(preferredAddon.url || ''); + const wouldBeUrl = queryParams ? `${baseUrl}/meta/${type}/${id}.json?${queryParams}` : `${baseUrl}/meta/${type}/${id}.json`; + logger.log(`🔍 Would check URL: ${wouldBeUrl} (addon: ${preferredAddon.name})`); + + // Log addon resources for debugging + logger.log(`🔍 Addon resources:`, JSON.stringify(preferredAddon.resources, null, 2)); + + // Check if addon supports meta resource for this type + let hasMetaSupport = false; + + for (const resource of preferredAddon.resources) { + // Check if the current element is a ResourceObject + if (typeof resource === 'object' && resource !== null && 'name' in resource) { + const typedResource = resource as ResourceObject; + if (typedResource.name === 'meta' && + Array.isArray(typedResource.types) && + typedResource.types.includes(type)) { + hasMetaSupport = true; + break; + } + } + // Check if the element is the simple string "meta" AND the addon has a top-level types array + else if (typeof resource === 'string' && resource === 'meta' && preferredAddon.types) { + if (Array.isArray(preferredAddon.types) && preferredAddon.types.includes(type)) { + hasMetaSupport = true; + break; + } + } + } + + logger.log(`🔍 Meta support check: ${hasMetaSupport} (addon types: ${JSON.stringify(preferredAddon.types)})`); + + if (hasMetaSupport) { + try { + logger.log(`HTTP GET: ${wouldBeUrl} (preferred addon: ${preferredAddon.name})`); + const response = await this.retryRequest(async () => { + return await axios.get(wouldBeUrl, { timeout: 10000 }); + }); + + if (response.data && response.data.meta) { + logger.log(`✅ Metadata fetched successfully from preferred addon: ${wouldBeUrl}`); + return response.data.meta; + } + } catch (error) { + logger.warn(`❌ Failed to fetch meta from preferred addon ${preferredAddon.name}:`, error); + } + } else { + logger.warn(`⚠️ Preferred addon ${preferredAddonId} does not support meta for type ${type}`); + } + } else { + logger.warn(`⚠️ Preferred addon ${preferredAddonId} not found or has no resources`); + } + } + // Try Cinemeta with different base URLs const cinemetaUrls = [ 'https://v3-cinemeta.strem.io', @@ -475,44 +564,66 @@ class StremioService { for (const baseUrl of cinemetaUrls) { try { const url = `${baseUrl}/meta/${type}/${id}.json`; + logger.log(`HTTP GET: ${url}`); const response = await this.retryRequest(async () => { return await axios.get(url, { timeout: 10000 }); }); if (response.data && response.data.meta) { + logger.log(`✅ Metadata fetched successfully from: ${url}`); return response.data.meta; } } catch (error) { - logger.warn(`Failed to fetch meta from ${baseUrl}:`, error); + logger.warn(`❌ Failed to fetch meta from ${baseUrl}:`, error); continue; // Try next URL } } - // If Cinemeta fails, try other addons - const addons = this.getInstalledAddons(); + // If Cinemeta fails, try other addons (excluding the preferred one already tried) for (const addon of addons) { - if (!addon.resources || addon.id === 'com.linvo.cinemeta') continue; + if (!addon.resources || addon.id === 'com.linvo.cinemeta' || addon.id === preferredAddonId) continue; - const metaResource = addon.resources.find( - resource => resource.name === 'meta' && resource.types.includes(type) - ); + // Check if addon supports meta resource for this type (handles both string and object formats) + let hasMetaSupport = false; - if (!metaResource) continue; + 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 ResourceObject; + if (typedResource.name === 'meta' && + Array.isArray(typedResource.types) && + typedResource.types.includes(type)) { + hasMetaSupport = true; + break; + } + } + // Check if the element is the simple string "meta" AND the addon has a top-level types array + else if (typeof resource === 'string' && resource === 'meta' && addon.types) { + if (Array.isArray(addon.types) && addon.types.includes(type)) { + hasMetaSupport = true; + break; + } + } + } + + if (!hasMetaSupport) 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(`HTTP GET: ${url}`); const response = await this.retryRequest(async () => { return await axios.get(url, { timeout: 10000 }); }); if (response.data && response.data.meta) { + logger.log(`✅ Metadata fetched successfully from: ${url}`); return response.data.meta; } } catch (error) { - logger.warn(`Failed to fetch meta from ${addon.name}:`, error); + logger.warn(`❌ Failed to fetch meta from ${addon.name} (${addon.id}):`, error); continue; // Try next addon } } diff --git a/src/types/metadata.ts b/src/types/metadata.ts index 84d3ebfe..aff9d856 100644 --- a/src/types/metadata.ts +++ b/src/types/metadata.ts @@ -81,6 +81,7 @@ export interface StreamingContent { name: string; description?: string; poster?: string; + posterShape?: string; banner?: string; logo?: string; year?: string | number; @@ -88,12 +89,30 @@ export interface StreamingContent { imdbRating?: string; genres?: string[]; director?: string; - writer?: string; + writer?: string[]; cast?: string[]; releaseInfo?: string; directors?: string[]; creators?: string[]; certification?: string; + released?: string; + trailerStreams?: any[]; + videos?: any[]; + inLibrary?: boolean; + // Enhanced metadata from addons + country?: string; + links?: Array<{ + name: string; + category: string; + url: string; + }>; + behaviorHints?: { + defaultVideoId?: string; + hasScheduledVideos?: boolean; + [key: string]: any; + }; + imdb_id?: string; + slug?: string; } // Navigation types diff --git a/src/types/navigation.d.ts b/src/types/navigation.d.ts index c01523fb..a69bd543 100644 --- a/src/types/navigation.d.ts +++ b/src/types/navigation.d.ts @@ -6,6 +6,7 @@ export type RootStackParamList = { Metadata: { id: string; type: string; + addonId?: string; }; Streams: { id: string;