From 408b1cb366661f7ec3c1d8a694c3d60d19598874 Mon Sep 17 00:00:00 2001 From: tapframe Date: Wed, 13 Aug 2025 01:49:05 +0530 Subject: [PATCH] mkv fix --- local-scrapers-repo | 2 +- src/components/player/VideoPlayer.tsx | 14 ++++++- src/navigation/AppNavigator.tsx | 1 + src/screens/StreamsScreen.tsx | 29 ++++++++++++++ src/services/localScraperService.ts | 55 +++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 3 deletions(-) diff --git a/local-scrapers-repo b/local-scrapers-repo index e63dbd7..0a040cc 160000 --- a/local-scrapers-repo +++ b/local-scrapers-repo @@ -1 +1 @@ -Subproject commit e63dbd7ed54a8a5eb16e57eb2fb41fad6bc96272 +Subproject commit 0a040cc12da805fa8b38411d128e607e86e0f919 diff --git a/src/components/player/VideoPlayer.tsx b/src/components/player/VideoPlayer.tsx index 1bc12af..863e8e5 100644 --- a/src/components/player/VideoPlayer.tsx +++ b/src/components/player/VideoPlayer.tsx @@ -57,8 +57,18 @@ const VideoPlayer: React.FC = () => { // Use AndroidVideoPlayer for: // - Android devices // - Xprime streams on any platform - // - Non-MKV files on iOS - if (Platform.OS === 'android' || isXprimeStream || (Platform.OS === 'ios' && !isMkvFile && !forceVlc)) { + // - Non-MKV files on iOS (unless forceVlc is set) + const shouldUseAndroidPlayer = Platform.OS === 'android' || isXprimeStream || (Platform.OS === 'ios' && !isMkvFile && !forceVlc); + logger.log('[VideoPlayer] Player selection:', { + platform: Platform.OS, + isXprimeStream, + isMkvFile, + forceVlc: !!forceVlc, + selected: shouldUseAndroidPlayer ? 'AndroidVideoPlayer' : 'VLCPlayer', + streamProvider, + uri + }); + if (shouldUseAndroidPlayer) { return ; } diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index a194166..60f3f9c 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -84,6 +84,7 @@ export type RootStackParamList = { streamProvider?: string; streamName?: string; headers?: { [key: string]: string }; + forceVlc?: boolean; id?: string; type?: string; episodeId?: string; diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx index 0eb567f..5d8dd80 100644 --- a/src/screens/StreamsScreen.tsx +++ b/src/screens/StreamsScreen.tsx @@ -840,6 +840,18 @@ export const StreamsScreen = () => { const streamName = stream.name || stream.title || 'Unnamed Stream'; const streamProvider = stream.addonId || stream.addonName || stream.name; + // Determine if we should force VLC on iOS based on provider-declared formats (e.g., MKV) + let forceVlc = false; + try { + const providerId = stream.addonId || (stream as any).addon; + if (Platform.OS === 'ios' && providerId) { + forceVlc = await localScraperService.supportsFormat(providerId, 'mkv'); + logger.log(`[StreamsScreen] Provider '${providerId}' MKV support -> ${forceVlc}`); + } + } catch (e) { + logger.warn('[StreamsScreen] MKV support detection failed:', e); + } + // Add pre-navigation orientation lock to reduce glitch try { await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE); @@ -863,6 +875,8 @@ export const StreamsScreen = () => { streamName: streamName, // Always prefer stream.headers; player will use these for requests headers: stream.headers || undefined, + // Force VLC for providers that declare MKV format support on iOS + forceVlc, id, type, episodeId: type === 'series' && selectedEpisode ? selectedEpisode : undefined, @@ -877,6 +891,21 @@ export const StreamsScreen = () => { const handleStreamPress = useCallback(async (stream: Stream) => { try { if (stream.url) { + // If provider declares MKV support, force the in-app VLC-based player on iOS + try { + const providerId = stream.addonId || (stream as any).addon; + if (Platform.OS === 'ios' && providerId) { + const providerRequiresVlc = await localScraperService.supportsFormat(providerId, 'mkv'); + if (providerRequiresVlc) { + logger.log(`[StreamsScreen] Forcing in-app VLC for provider '${providerId}' on iOS due to MKV support`); + navigateToPlayer(stream); + return; + } + } + } catch (err) { + logger.warn('[StreamsScreen] MKV pre-check failed:', err); + } + logger.log('handleStreamPress called with stream:', { url: stream.url, behaviorHints: stream.behaviorHints, diff --git a/src/services/localScraperService.ts b/src/services/localScraperService.ts index c77489e..ce04c86 100644 --- a/src/services/localScraperService.ts +++ b/src/services/localScraperService.ts @@ -27,6 +27,10 @@ export interface ScraperInfo { manifestEnabled?: boolean; // Whether the scraper is enabled in the manifest supportedPlatforms?: ('ios' | 'android')[]; // Platforms where this scraper is supported disabledPlatforms?: ('ios' | 'android')[]; // Platforms where this scraper is disabled + // Optional list of supported output formats for this provider (e.g., ["mkv", "mp4"]). + // We support both `formats` and `supportedFormats` keys for manifest flexibility. + formats?: string[]; + supportedFormats?: string[]; } export interface LocalScraperResult { @@ -103,6 +107,16 @@ class LocalScraperService { if (!scraper.supportedTypes || !Array.isArray(scraper.supportedTypes)) { scraper.supportedTypes = ['movie', 'tv']; // Default to both types } + // Normalize formats fields (support both `formats` and `supportedFormats`) + if (typeof (scraper as any).formats === 'string') { + scraper.formats = [(scraper as any).formats as unknown as string]; + } + if (typeof (scraper as any).supportedFormats === 'string') { + scraper.supportedFormats = [(scraper as any).supportedFormats as unknown as string]; + } + if (!scraper.supportedFormats && scraper.formats) { + scraper.supportedFormats = scraper.formats; + } // Ensure other required fields have defaults if (!scraper.description) { @@ -331,6 +345,16 @@ class LocalScraperService { if (!updatedScraperInfo.supportedTypes || !Array.isArray(updatedScraperInfo.supportedTypes)) { updatedScraperInfo.supportedTypes = ['movie', 'tv']; // Default to both types } + // Normalize formats fields (support both `formats` and `supportedFormats`) + if (typeof (updatedScraperInfo as any).formats === 'string') { + updatedScraperInfo.formats = [(updatedScraperInfo as any).formats as unknown as string]; + } + if (typeof (updatedScraperInfo as any).supportedFormats === 'string') { + updatedScraperInfo.supportedFormats = [(updatedScraperInfo as any).supportedFormats as unknown as string]; + } + if (!updatedScraperInfo.supportedFormats && updatedScraperInfo.formats) { + updatedScraperInfo.supportedFormats = updatedScraperInfo.formats; + } this.installedScrapers.set(scraperInfo.id, updatedScraperInfo); @@ -431,6 +455,18 @@ class LocalScraperService { // If manifest says enabled: true, use installed state or default to false enabled: scraperInfo.enabled ? (installedScraper?.enabled ?? false) : false }; + + // Normalize formats fields (support both `formats` and `supportedFormats`) + const anyScraper: any = scraperWithManifestData as any; + if (typeof anyScraper.formats === 'string') { + anyScraper.formats = [anyScraper.formats]; + } + if (typeof anyScraper.supportedFormats === 'string') { + anyScraper.supportedFormats = [anyScraper.supportedFormats]; + } + if (!anyScraper.supportedFormats && anyScraper.formats) { + anyScraper.supportedFormats = anyScraper.formats; + } return scraperWithManifestData; }); @@ -451,6 +487,25 @@ class LocalScraperService { } } + // Check if a given scraper declares support for a specific format (e.g., 'mkv') + async supportsFormat(scraperId: string, format: string): Promise { + await this.ensureInitialized(); + try { + const available = await this.getAvailableScrapers(); + const info = available.find(s => s.id === scraperId); + if (!info) return false; + const formats = (info.supportedFormats || info.formats || []) + .filter(Boolean) + .map(f => (typeof f === 'string' ? f.toLowerCase() : String(f).toLowerCase())); + const supported = formats.includes((format || '').toLowerCase()); + logger.log(`[LocalScraperService] supportsFormat('${scraperId}', '${format}') -> ${supported}. Formats: ${JSON.stringify(formats)}`); + return supported; + } catch (e) { + logger.warn(`[LocalScraperService] supportsFormat('${scraperId}', '${format}') failed`, e); + return false; + } + } + // Enable/disable scraper async setScraperEnabled(scraperId: string, enabled: boolean): Promise { await this.ensureInitialized();