Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions src/hooks/useMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1877,6 +1877,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
}

// Get TMDB ID for external sources and determine the correct ID for Stremio addons
const isImdb = id.startsWith('tt');
if (__DEV__) console.log('🔍 [loadEpisodeStreams] Getting TMDB ID for:', id);
let tmdbId;
let stremioEpisodeId = episodeId; // Default to original episode ID
Expand All @@ -1901,19 +1902,25 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const cleanEpisodeId = episodeId.replace(/^series:/, '');
const parts = cleanEpisodeId.split(':');

if (parts[0] === 'kitsu' && parts.length === 3) {
// kitsu:animeId:episode — no season segment
if (isImdb && parts.length === 3) {
// Format: ttXXX:season:episode
showIdStr = parts[0];
seasonNum = parts[1];
episodeNum = parts[2];
} else if (!isImdb && parts.length === 3) {
// Format: prefix:id:episode (no season for MAL/Kitsu/etc)
showIdStr = `${parts[0]}:${parts[1]}`;
episodeNum = parts[2];
seasonNum = '';
} else if (parts.length >= 3) {
episodeNum = parts.pop() || '';
seasonNum = parts.pop() || '';
showIdStr = parts.join(':');
} else if (parts.length === 2) {
showIdStr = parts[0];
episodeNum = parts[1];
seasonNum = '';
} else if (parts.length >= 4) {
// Format: prefix:id:season:episode - it is possible that some addons use it
episodeNum = parts.pop() || '';
seasonNum = parts.pop() || '';
showIdStr = parts.join(':');
}

if (__DEV__) console.log(`🔍 [loadEpisodeStreams] Parsed ID: show=${showIdStr}, s=${seasonNum}, e=${episodeNum}`);
Expand Down Expand Up @@ -1976,7 +1983,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
if (__DEV__) console.log('⚠️ [loadEpisodeStreams] Failed to convert TMDB to IMDb, using TMDB episode ID:', error);
}
}
} else if (id.startsWith('tt')) {
} else if (isImdb) {
// This is already an IMDB ID, perfect for Stremio
if (settings.enrichMetadataWithTMDB) {
if (__DEV__) console.log('📝 [loadEpisodeStreams] Converting IMDB ID to TMDB ID...');
Expand Down
71 changes: 40 additions & 31 deletions src/screens/MetadataScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -586,13 +586,17 @@ const MetadataScreen: React.FC = () => {

const handleShowStreams = useCallback(() => {
const { watchProgress } = watchProgressData;
const isImdb = id.startsWith('tt');

// Ensure trailer stops immediately before navigating to Streams
try { pauseTrailer(); } catch { }

// Helper to build episodeId from episode object
const buildEpisodeId = (ep: any): string => {
return ep.stremioId || `${id}:${ep.season_number}:${ep.episode_number}`;
if (ep.stremioId) return ep.stremioId;
return isImdb
? `${id}:${ep.season_number}:${ep.episode_number}`
: `${id}:${ep.episode_number}`;
};

if (Object.keys(groupedEpisodes).length > 0) {
Expand All @@ -611,38 +615,28 @@ const MetadataScreen: React.FC = () => {

const parts = watchProgress.episodeId.split(':');

if (parts.length === 3) {
// showId:season:episode
currentSeason = parseInt(parts[1], 10);
currentEpisode = parseInt(parts[2], 10);
} else if (parts.length === 2) {
// season:episode
currentSeason = parseInt(parts[0], 10);
currentEpisode = parseInt(parts[1], 10);
} else {
// pattern like s5e01
const match = watchProgress.episodeId.match(/s(\d+)e(\d+)/i);
if (match) {
currentSeason = parseInt(match[1], 10);
currentEpisode = parseInt(match[2], 10);
if (isImdb) {
if (parts.length === 3) {
currentSeason = parseInt(parts[1], 10);
currentEpisode = parseInt(parts[2], 10);
} else if (parts.length === 2) {
currentEpisode = parseInt(parts[1], 10);
}
} else {
currentEpisode = parts.length === 3 ? parseInt(parts[2], 10) : null;
}

if (currentSeason !== null && currentEpisode !== null) {
// DIRECT APPROACH: Just create the next episode ID directly
// This ensures we navigate to the next episode even if it's not yet in our episodes array
const nextEpisodeId = `${id}:${currentSeason}:${currentEpisode + 1}`;
if (__DEV__) console.log(`[MetadataScreen] Created next episode ID directly: ${nextEpisodeId}`);

// Still try to find the episode in our list to verify it exists
const nextEpisodeExists = episodes.some(ep =>
ep.season_number === currentSeason && ep.episode_number === (currentEpisode + 1)
);
if (currentEpisode !== null) {
const nextEpisodeId = isImdb
? `${id}:${currentSeason || episodes[0]?.season_number || 1}:${currentEpisode + 1}`
: `${id}:${currentEpisode + 1}`;
if (__DEV__) console.log(`[MetadataScreen] Created next episode ID: ${nextEpisodeId}`);

const nextEpisodeExists = episodes.some(ep => ep.episode_number === (currentEpisode + 1));
if (nextEpisodeExists) {
if (__DEV__) console.log(`[MetadataScreen] Verified next episode S${currentSeason}E${currentEpisode + 1} exists in episodes list`);
if (__DEV__) console.log(`[MetadataScreen] Verified next episode exists`);
} else {
if (__DEV__) console.log(`[MetadataScreen] Warning: Next episode S${currentSeason}E${currentEpisode + 1} not found in episodes list, but proceeding anyway`);
if (__DEV__) console.log(`[MetadataScreen] Warning: Next episode not found`);
}

targetEpisodeId = nextEpisodeId;
Expand All @@ -656,10 +650,14 @@ const MetadataScreen: React.FC = () => {
}

if (targetEpisodeId) {
// Ensure the episodeId has showId prefix (id:season:episode)
// Ensure the episodeId has showId prefix (id:season:episode or id:episode)
const epParts = targetEpisodeId.split(':');
let normalizedEpisodeId = targetEpisodeId;
if (epParts.length === 2) {

if (epParts.length === 2 && !isImdb) {
normalizedEpisodeId = `${id}:${epParts[1]}`;
}
else if (epParts.length === 2 && isImdb) {
normalizedEpisodeId = `${id}:${epParts[0]}:${epParts[1]}`;
}
if (__DEV__) console.log(`[MetadataScreen] Navigating to streams with episodeId: ${normalizedEpisodeId}`);
Expand All @@ -672,7 +670,9 @@ const MetadataScreen: React.FC = () => {
let fallbackEpisodeId = episodeId;
if (episodeId && episodeId.split(':').length === 2) {
const p = episodeId.split(':');
fallbackEpisodeId = `${id}:${p[0]}:${p[1]}`;
if (!p[0].startsWith('tt')) {
fallbackEpisodeId = isImdb ? `${id}:${p[0]}:${p[1]}` : `${id}:${p[1]}`;
}
}
if (__DEV__) console.log(`[MetadataScreen] Navigating with fallback episodeId: ${fallbackEpisodeId}`);
navigation.navigate('Streams', { id, type, episodeId: fallbackEpisodeId });
Expand All @@ -682,7 +682,16 @@ const MetadataScreen: React.FC = () => {
if (!isScreenFocused) return;

if (__DEV__) console.log('[MetadataScreen] Selected Episode:', episode.episode_number, episode.season_number);
const episodeId = episode.stremioId || `${id}:${episode.season_number}:${episode.episode_number}`;

let episodeId: string;
if (episode.stremioId) {
episodeId = episode.stremioId;
} else {
const isImdb = id.startsWith('tt');
episodeId = isImdb
? `${id}:${episode.season_number}:${episode.episode_number}`
: `${id}:${episode.episode_number}`;
}

// Optimize navigation with requestAnimationFrame
requestAnimationFrame(() => {
Expand Down
13 changes: 11 additions & 2 deletions src/services/stremioService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,17 @@ class StremioService {
return true;
}

// Check if the ID matches any supported prefix
return supportedPrefixes.some(prefix => lowerId.startsWith(prefix.toLowerCase()));
// Check if the ID matches any supported prefix.
// For prefixes without a trailing separator (e.g. "mal", "kitsu"), the ID must be
// longer than the prefix itself so that bare prefix strings like "mal" are rejected.
const result = supportedPrefixes.some(prefix => {
const lowerPrefix = prefix.toLowerCase();
if (!lowerId.startsWith(lowerPrefix)) return false;
if (lowerPrefix.endsWith(':') || lowerPrefix.endsWith('_')) return true;
return lowerId.length > lowerPrefix.length;
});
if (__DEV__) console.log(`🔍 [isValidContentId] Prefix match result: ${result} for ID '${id}'`);
return result;
}

// Get all content types supported by installed addons
Expand Down
11 changes: 8 additions & 3 deletions src/services/tmdbService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,11 +529,16 @@ export class TMDBService {
*/
async extractTMDBIdFromStremioId(stremioId: string): Promise<number | null> {
try {
// Extract the base IMDB ID (remove season/episode info if present)
const imdbId = stremioId.split(':')[0];
// Extract the base ID (remove season/episode info if present)
const baseId = stremioId.split(':')[0];

// Only try to convert if it's an IMDb ID (starts with 'tt')
if (!baseId.startsWith('tt')) {
return null;
}

// Use the existing findTMDBIdByIMDB function to get the TMDB ID
const tmdbId = await this.findTMDBIdByIMDB(imdbId);
const tmdbId = await this.findTMDBIdByIMDB(baseId);
return tmdbId;
} catch (error) {
return null;
Expand Down