mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 00:32:04 +00:00
Merge pull request #485 from skoruppa/main
Prioritize external meta addons & fix episode ID parsing
This commit is contained in:
commit
9cab1f4f1e
5 changed files with 142 additions and 40 deletions
|
|
@ -734,44 +734,119 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
||||||
let contentResult = null;
|
let contentResult = null;
|
||||||
let lastError = null;
|
let lastError = null;
|
||||||
|
|
||||||
// Try with original ID first
|
// Check if user prefers external meta addons
|
||||||
try {
|
const preferExternal = settings.preferExternalMetaAddonDetail;
|
||||||
console.log('🔍 [useMetadata] Attempting metadata fetch with original ID:', { type, actualId, addonId });
|
|
||||||
const [content, castData] = await Promise.allSettled([
|
|
||||||
// Load content with timeout and retry
|
|
||||||
withRetry(async () => {
|
|
||||||
console.log('🔍 [useMetadata] Calling catalogService.getEnhancedContentDetails:', { type, actualId, addonId });
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
console.log('🔍 [useMetadata] catalogService.getEnhancedContentDetails result:', {
|
|
||||||
hasResult: Boolean(result),
|
|
||||||
resultId: result?.id,
|
|
||||||
resultName: result?.name,
|
|
||||||
resultType: result?.type
|
|
||||||
});
|
|
||||||
if (__DEV__) logger.log('[loadMetadata] addon metadata fetched', { hasResult: Boolean(result) });
|
|
||||||
return result;
|
|
||||||
}),
|
|
||||||
// Start loading cast immediately in parallel
|
|
||||||
loadCast()
|
|
||||||
]);
|
|
||||||
|
|
||||||
contentResult = content;
|
if (preferExternal) {
|
||||||
if (content.status === 'fulfilled' && content.value) {
|
// Try external meta addons first
|
||||||
console.log('🔍 [useMetadata] Successfully got metadata with original ID');
|
try {
|
||||||
} else {
|
console.log('🔍 [useMetadata] Trying external meta addons first');
|
||||||
console.log('🔍 [useMetadata] Original ID failed, will try fallback conversion');
|
const [content, castData] = await Promise.allSettled([
|
||||||
lastError = (content as any)?.reason;
|
withRetry(async () => {
|
||||||
|
// Get all installed addons
|
||||||
|
const allAddons = await stremioService.getInstalledAddonsAsync();
|
||||||
|
|
||||||
|
// Find catalog addon index
|
||||||
|
const catalogAddonIndex = allAddons.findIndex(addon => addon.id === addonId);
|
||||||
|
|
||||||
|
// Filter for meta addons that are BEFORE catalog addon in priority
|
||||||
|
const externalMetaAddons = allAddons
|
||||||
|
.slice(0, catalogAddonIndex >= 0 ? catalogAddonIndex : allAddons.length)
|
||||||
|
.filter(addon => {
|
||||||
|
if (!addon.resources || !Array.isArray(addon.resources)) return false;
|
||||||
|
|
||||||
|
return addon.resources.some(resource => {
|
||||||
|
if (typeof resource === 'string') return resource === 'meta';
|
||||||
|
return (resource as any).name === 'meta';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Try each external meta addon in priority order
|
||||||
|
for (const addon of externalMetaAddons) {
|
||||||
|
try {
|
||||||
|
const result = await withTimeout(
|
||||||
|
stremioService.getMetaDetails(type, actualId, addon.id),
|
||||||
|
API_TIMEOUT
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
console.log('🔍 [useMetadata] Got metadata from external addon:', addon.name);
|
||||||
|
if (actualId.startsWith('tt')) {
|
||||||
|
setImdbId(actualId);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('🔍 [useMetadata] External addon failed:', addon.name, error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no external addon worked, fall back to catalog addon
|
||||||
|
console.log('🔍 [useMetadata] No external meta addon worked, falling back to catalog addon');
|
||||||
|
const result = await withTimeout(
|
||||||
|
catalogService.getEnhancedContentDetails(type, actualId, addonId),
|
||||||
|
API_TIMEOUT
|
||||||
|
);
|
||||||
|
if (actualId.startsWith('tt')) {
|
||||||
|
setImdbId(actualId);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
loadCast()
|
||||||
|
]);
|
||||||
|
|
||||||
|
contentResult = content;
|
||||||
|
if (content.status === 'fulfilled' && content.value) {
|
||||||
|
console.log('🔍 [useMetadata] Successfully got metadata with external meta addon priority');
|
||||||
|
} else {
|
||||||
|
console.log('🔍 [useMetadata] External meta addon priority failed, will try fallback');
|
||||||
|
lastError = (content as any)?.reason;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('🔍 [useMetadata] External meta addon attempt failed:', { error: error instanceof Error ? error.message : String(error) });
|
||||||
|
lastError = error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Original behavior: try with original ID first
|
||||||
|
try {
|
||||||
|
console.log('🔍 [useMetadata] Attempting metadata fetch with original ID:', { type, actualId, addonId });
|
||||||
|
const [content, castData] = await Promise.allSettled([
|
||||||
|
// Load content with timeout and retry
|
||||||
|
withRetry(async () => {
|
||||||
|
console.log('🔍 [useMetadata] Calling catalogService.getEnhancedContentDetails:', { type, actualId, addonId });
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
console.log('🔍 [useMetadata] catalogService.getEnhancedContentDetails result:', {
|
||||||
|
hasResult: Boolean(result),
|
||||||
|
resultId: result?.id,
|
||||||
|
resultName: result?.name,
|
||||||
|
resultType: result?.type
|
||||||
|
});
|
||||||
|
if (__DEV__) logger.log('[loadMetadata] addon metadata fetched', { hasResult: Boolean(result) });
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
// Start loading cast immediately in parallel
|
||||||
|
loadCast()
|
||||||
|
]);
|
||||||
|
|
||||||
|
contentResult = content;
|
||||||
|
if (content.status === 'fulfilled' && content.value) {
|
||||||
|
console.log('🔍 [useMetadata] Successfully got metadata with original ID');
|
||||||
|
} else {
|
||||||
|
console.log('🔍 [useMetadata] Original ID failed, will try fallback conversion');
|
||||||
|
lastError = (content as any)?.reason;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('🔍 [useMetadata] Original ID attempt failed:', { error: error instanceof Error ? error.message : String(error) });
|
||||||
|
lastError = error;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.log('🔍 [useMetadata] Original ID attempt failed:', { error: error instanceof Error ? error.message : String(error) });
|
|
||||||
lastError = error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If original TMDB ID failed and enrichment is disabled, try ID conversion as fallback
|
// If original TMDB ID failed and enrichment is disabled, try ID conversion as fallback
|
||||||
|
|
@ -1831,10 +1906,10 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
||||||
seasonNum = parts.pop() || '';
|
seasonNum = parts.pop() || '';
|
||||||
showIdStr = parts.join(':');
|
showIdStr = parts.join(':');
|
||||||
} else if (parts.length === 2) {
|
} else if (parts.length === 2) {
|
||||||
// Edge case: maybe just id:episode? unlikely but safe fallback
|
// For IDs like mal:57658:1, this is showId:episode (no season)
|
||||||
episodeNum = parts[1];
|
|
||||||
seasonNum = '1'; // Default
|
|
||||||
showIdStr = parts[0];
|
showIdStr = parts[0];
|
||||||
|
episodeNum = parts[1];
|
||||||
|
seasonNum = ''; // No season for this format
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__DEV__) console.log(`🔍 [loadEpisodeStreams] Parsed ID: show=${showIdStr}, s=${seasonNum}, e=${episodeNum}`);
|
if (__DEV__) console.log(`🔍 [loadEpisodeStreams] Parsed ID: show=${showIdStr}, s=${seasonNum}, e=${episodeNum}`);
|
||||||
|
|
@ -1912,6 +1987,9 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
||||||
// This handles cases where 'tt' is used for a unique episode ID directly
|
// This handles cases where 'tt' is used for a unique episode ID directly
|
||||||
if (!seasonNum && !episodeNum) {
|
if (!seasonNum && !episodeNum) {
|
||||||
stremioEpisodeId = episodeId;
|
stremioEpisodeId = episodeId;
|
||||||
|
} else if (!seasonNum) {
|
||||||
|
// No season (e.g., mal:57658:1) - use id:episode format
|
||||||
|
stremioEpisodeId = `${id}:${episodeNum}`;
|
||||||
} else {
|
} else {
|
||||||
stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`;
|
stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`;
|
||||||
}
|
}
|
||||||
|
|
@ -1923,6 +2001,9 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
||||||
if (!seasonNum && !episodeNum) {
|
if (!seasonNum && !episodeNum) {
|
||||||
// Remove 'series:' prefix if present to be safe, though parsing logic above usually handles it
|
// Remove 'series:' prefix if present to be safe, though parsing logic above usually handles it
|
||||||
stremioEpisodeId = episodeId.replace(/^series:/, '');
|
stremioEpisodeId = episodeId.replace(/^series:/, '');
|
||||||
|
} else if (!seasonNum) {
|
||||||
|
// No season (e.g., mal:57658:1) - use id:episode format
|
||||||
|
stremioEpisodeId = `${id}:${episodeNum}`;
|
||||||
} else {
|
} else {
|
||||||
stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`;
|
stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,8 @@ export interface AppSettings {
|
||||||
preferredAudioLanguage: string; // Preferred language for audio tracks (ISO 639-1 code)
|
preferredAudioLanguage: string; // Preferred language for audio tracks (ISO 639-1 code)
|
||||||
subtitleSourcePreference: 'internal' | 'external' | 'any'; // Prefer internal (embedded), external (addon), or any
|
subtitleSourcePreference: 'internal' | 'external' | 'any'; // Prefer internal (embedded), external (addon), or any
|
||||||
enableSubtitleAutoSelect: boolean; // Auto-select subtitles based on preferences
|
enableSubtitleAutoSelect: boolean; // Auto-select subtitles based on preferences
|
||||||
|
// External metadata addon preference
|
||||||
|
preferExternalMetaAddonDetail: boolean; // Prefer metadata from external meta addons on detail page
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: AppSettings = {
|
export const DEFAULT_SETTINGS: AppSettings = {
|
||||||
|
|
@ -203,6 +205,8 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
||||||
preferredAudioLanguage: 'en', // Default to English audio
|
preferredAudioLanguage: 'en', // Default to English audio
|
||||||
subtitleSourcePreference: 'internal', // Prefer internal/embedded subtitles first
|
subtitleSourcePreference: 'internal', // Prefer internal/embedded subtitles first
|
||||||
enableSubtitleAutoSelect: true, // Auto-select subtitles by default
|
enableSubtitleAutoSelect: true, // Auto-select subtitles by default
|
||||||
|
// External metadata addon preference
|
||||||
|
preferExternalMetaAddonDetail: false, // Disabled by default
|
||||||
};
|
};
|
||||||
|
|
||||||
const SETTINGS_STORAGE_KEY = 'app_settings';
|
const SETTINGS_STORAGE_KEY = 'app_settings';
|
||||||
|
|
|
||||||
|
|
@ -983,6 +983,8 @@
|
||||||
"select_catalogs": "Select Catalogs",
|
"select_catalogs": "Select Catalogs",
|
||||||
"all_catalogs": "All catalogs",
|
"all_catalogs": "All catalogs",
|
||||||
"selected": "selected",
|
"selected": "selected",
|
||||||
|
"prefer_external_meta": "Prefer External Meta Addon",
|
||||||
|
"prefer_external_meta_desc": "Use external metadata on detail page",
|
||||||
"hero_layout": "Hero Layout",
|
"hero_layout": "Hero Layout",
|
||||||
"layout_legacy": "Legacy",
|
"layout_legacy": "Legacy",
|
||||||
"layout_carousel": "Carousel",
|
"layout_carousel": "Carousel",
|
||||||
|
|
|
||||||
|
|
@ -983,6 +983,8 @@
|
||||||
"select_catalogs": "Wybierz katalogi",
|
"select_catalogs": "Wybierz katalogi",
|
||||||
"all_catalogs": "Wszystkie katalogi",
|
"all_catalogs": "Wszystkie katalogi",
|
||||||
"selected": "wybrane",
|
"selected": "wybrane",
|
||||||
|
"prefer_external_meta": "Preferuj zewnętrzny dodatek meta",
|
||||||
|
"prefer_external_meta_desc": "Używaj zewnętrznych metadanych na stronie szczegółów",
|
||||||
"hero_layout": "Układ sekcji Hero",
|
"hero_layout": "Układ sekcji Hero",
|
||||||
"layout_legacy": "Klasyczny",
|
"layout_legacy": "Klasyczny",
|
||||||
"layout_carousel": "Karuzela",
|
"layout_carousel": "Karuzela",
|
||||||
|
|
|
||||||
|
|
@ -344,9 +344,22 @@ const HomeScreenSettings: React.FC = () => {
|
||||||
colors={colors}
|
colors={colors}
|
||||||
renderControl={ChevronRight}
|
renderControl={ChevronRight}
|
||||||
onPress={() => navigation.navigate('HeroCatalogs')}
|
onPress={() => navigation.navigate('HeroCatalogs')}
|
||||||
isLast={true}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<SettingItem
|
||||||
|
title={t("home_screen.prefer_external_meta")}
|
||||||
|
description={t("home_screen.prefer_external_meta_desc")}
|
||||||
|
icon="cloud-download"
|
||||||
|
isDarkMode={isDarkMode}
|
||||||
|
colors={colors}
|
||||||
|
renderControl={() => (
|
||||||
|
<CustomSwitch
|
||||||
|
value={settings.preferExternalMetaAddonDetail}
|
||||||
|
onValueChange={(value) => handleUpdateSetting('preferExternalMetaAddonDetail', value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
isLast={true}
|
||||||
|
/>
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
|
|
||||||
{settings.showHeroSection && (
|
{settings.showHeroSection && (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue