mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 08:41:57 +00:00
mkv fix
This commit is contained in:
parent
876f77019e
commit
408b1cb366
5 changed files with 98 additions and 3 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit e63dbd7ed54a8a5eb16e57eb2fb41fad6bc96272
|
Subproject commit 0a040cc12da805fa8b38411d128e607e86e0f919
|
||||||
|
|
@ -57,8 +57,18 @@ const VideoPlayer: React.FC = () => {
|
||||||
// Use AndroidVideoPlayer for:
|
// Use AndroidVideoPlayer for:
|
||||||
// - Android devices
|
// - Android devices
|
||||||
// - Xprime streams on any platform
|
// - Xprime streams on any platform
|
||||||
// - Non-MKV files on iOS
|
// - Non-MKV files on iOS (unless forceVlc is set)
|
||||||
if (Platform.OS === 'android' || isXprimeStream || (Platform.OS === 'ios' && !isMkvFile && !forceVlc)) {
|
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 />;
|
return <AndroidVideoPlayer />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ export type RootStackParamList = {
|
||||||
streamProvider?: string;
|
streamProvider?: string;
|
||||||
streamName?: string;
|
streamName?: string;
|
||||||
headers?: { [key: string]: string };
|
headers?: { [key: string]: string };
|
||||||
|
forceVlc?: boolean;
|
||||||
id?: string;
|
id?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
episodeId?: string;
|
episodeId?: string;
|
||||||
|
|
|
||||||
|
|
@ -840,6 +840,18 @@ export const StreamsScreen = () => {
|
||||||
const streamName = stream.name || stream.title || 'Unnamed Stream';
|
const streamName = stream.name || stream.title || 'Unnamed Stream';
|
||||||
const streamProvider = stream.addonId || stream.addonName || stream.name;
|
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
|
// Add pre-navigation orientation lock to reduce glitch
|
||||||
try {
|
try {
|
||||||
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
|
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
|
||||||
|
|
@ -863,6 +875,8 @@ export const StreamsScreen = () => {
|
||||||
streamName: streamName,
|
streamName: streamName,
|
||||||
// Always prefer stream.headers; player will use these for requests
|
// Always prefer stream.headers; player will use these for requests
|
||||||
headers: stream.headers || undefined,
|
headers: stream.headers || undefined,
|
||||||
|
// Force VLC for providers that declare MKV format support on iOS
|
||||||
|
forceVlc,
|
||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
episodeId: type === 'series' && selectedEpisode ? selectedEpisode : undefined,
|
episodeId: type === 'series' && selectedEpisode ? selectedEpisode : undefined,
|
||||||
|
|
@ -877,6 +891,21 @@ export const StreamsScreen = () => {
|
||||||
const handleStreamPress = useCallback(async (stream: Stream) => {
|
const handleStreamPress = useCallback(async (stream: Stream) => {
|
||||||
try {
|
try {
|
||||||
if (stream.url) {
|
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:', {
|
logger.log('handleStreamPress called with stream:', {
|
||||||
url: stream.url,
|
url: stream.url,
|
||||||
behaviorHints: stream.behaviorHints,
|
behaviorHints: stream.behaviorHints,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,10 @@ export interface ScraperInfo {
|
||||||
manifestEnabled?: boolean; // Whether the scraper is enabled in the manifest
|
manifestEnabled?: boolean; // Whether the scraper is enabled in the manifest
|
||||||
supportedPlatforms?: ('ios' | 'android')[]; // Platforms where this scraper is supported
|
supportedPlatforms?: ('ios' | 'android')[]; // Platforms where this scraper is supported
|
||||||
disabledPlatforms?: ('ios' | 'android')[]; // Platforms where this scraper is disabled
|
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 {
|
export interface LocalScraperResult {
|
||||||
|
|
@ -103,6 +107,16 @@ class LocalScraperService {
|
||||||
if (!scraper.supportedTypes || !Array.isArray(scraper.supportedTypes)) {
|
if (!scraper.supportedTypes || !Array.isArray(scraper.supportedTypes)) {
|
||||||
scraper.supportedTypes = ['movie', 'tv']; // Default to both types
|
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
|
// Ensure other required fields have defaults
|
||||||
if (!scraper.description) {
|
if (!scraper.description) {
|
||||||
|
|
@ -331,6 +345,16 @@ class LocalScraperService {
|
||||||
if (!updatedScraperInfo.supportedTypes || !Array.isArray(updatedScraperInfo.supportedTypes)) {
|
if (!updatedScraperInfo.supportedTypes || !Array.isArray(updatedScraperInfo.supportedTypes)) {
|
||||||
updatedScraperInfo.supportedTypes = ['movie', 'tv']; // Default to both types
|
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);
|
this.installedScrapers.set(scraperInfo.id, updatedScraperInfo);
|
||||||
|
|
||||||
|
|
@ -431,6 +455,18 @@ class LocalScraperService {
|
||||||
// If manifest says enabled: true, use installed state or default to false
|
// If manifest says enabled: true, use installed state or default to false
|
||||||
enabled: scraperInfo.enabled ? (installedScraper?.enabled ?? false) : 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;
|
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
|
// Enable/disable scraper
|
||||||
async setScraperEnabled(scraperId: string, enabled: boolean): Promise<void> {
|
async setScraperEnabled(scraperId: string, enabled: boolean): Promise<void> {
|
||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue