This commit is contained in:
tapframe 2025-08-13 01:49:05 +05:30
parent 876f77019e
commit 408b1cb366
5 changed files with 98 additions and 3 deletions

@ -1 +1 @@
Subproject commit e63dbd7ed54a8a5eb16e57eb2fb41fad6bc96272
Subproject commit 0a040cc12da805fa8b38411d128e607e86e0f919

View file

@ -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 <AndroidVideoPlayer />;
}

View file

@ -84,6 +84,7 @@ export type RootStackParamList = {
streamProvider?: string;
streamName?: string;
headers?: { [key: string]: string };
forceVlc?: boolean;
id?: string;
type?: string;
episodeId?: string;

View file

@ -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,

View file

@ -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<boolean> {
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<void> {
await this.ensureInitialized();