mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 16:51:57 +00:00
some ui changes
This commit is contained in:
parent
8cf3508e22
commit
f209b538f3
5 changed files with 135 additions and 54 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit d129a1d2799397738c6e4848a6b80314a3326bd9
|
Subproject commit b69f3233879c55439465d4669bb786f14121573a
|
||||||
|
|
@ -711,55 +711,112 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleError = (error: any) => {
|
const handleError = (error: any) => {
|
||||||
logger.error('AndroidVideoPlayer error: ', error);
|
try {
|
||||||
|
logger.error('AndroidVideoPlayer error: ', error);
|
||||||
// Check for specific AVFoundation server configuration errors
|
|
||||||
const isServerConfigError = error?.error?.code === -11850 ||
|
// Early return if component is unmounted to prevent iOS crashes
|
||||||
error?.code === -11850 ||
|
if (!isMounted.current) {
|
||||||
(error?.error?.localizedDescription &&
|
logger.warn('[AndroidVideoPlayer] Component unmounted, skipping error handling');
|
||||||
error.error.localizedDescription.includes('server is not correctly configured'));
|
return;
|
||||||
|
}
|
||||||
// Format error details for user display
|
|
||||||
let errorMessage = 'An unknown error occurred';
|
// Check for specific AVFoundation server configuration errors
|
||||||
if (error) {
|
const isServerConfigError = error?.error?.code === -11850 ||
|
||||||
if (isServerConfigError) {
|
error?.code === -11850 ||
|
||||||
errorMessage = 'Stream server configuration issue. This may be a temporary problem with the video source.';
|
(error?.error?.localizedDescription &&
|
||||||
} else if (typeof error === 'string') {
|
error.error.localizedDescription.includes('server is not correctly configured'));
|
||||||
errorMessage = error;
|
|
||||||
} else if (error.message) {
|
// Format error details for user display
|
||||||
errorMessage = error.message;
|
let errorMessage = 'An unknown error occurred';
|
||||||
} else if (error.error && error.error.message) {
|
if (error) {
|
||||||
errorMessage = error.error.message;
|
if (isServerConfigError) {
|
||||||
} else if (error.error && error.error.localizedDescription) {
|
errorMessage = 'Stream server configuration issue. This may be a temporary problem with the video source.';
|
||||||
errorMessage = error.error.localizedDescription;
|
} else if (typeof error === 'string') {
|
||||||
} else if (error.code) {
|
errorMessage = error;
|
||||||
errorMessage = `Error Code: ${error.code}`;
|
} else if (error.message) {
|
||||||
} else {
|
errorMessage = error.message;
|
||||||
errorMessage = JSON.stringify(error, null, 2);
|
} else if (error.error && error.error.message) {
|
||||||
|
errorMessage = error.error.message;
|
||||||
|
} else if (error.error && error.error.localizedDescription) {
|
||||||
|
errorMessage = error.error.localizedDescription;
|
||||||
|
} else if (error.code) {
|
||||||
|
errorMessage = `Error Code: ${error.code}`;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
errorMessage = JSON.stringify(error, null, 2);
|
||||||
|
} catch (jsonError) {
|
||||||
|
errorMessage = 'Error occurred but details could not be serialized';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use safeSetState to prevent crashes on iOS when component is unmounted
|
||||||
|
safeSetState(() => {
|
||||||
|
setErrorDetails(errorMessage);
|
||||||
|
setShowErrorModal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear any existing timeout
|
||||||
|
if (errorTimeoutRef.current) {
|
||||||
|
clearTimeout(errorTimeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-exit after 5 seconds if user doesn't dismiss
|
||||||
|
errorTimeoutRef.current = setTimeout(() => {
|
||||||
|
if (isMounted.current) {
|
||||||
|
handleErrorExit();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
} catch (handlerError) {
|
||||||
|
// Fallback error handling to prevent crashes during error processing
|
||||||
|
logger.error('[AndroidVideoPlayer] Error in error handler:', handlerError);
|
||||||
|
if (isMounted.current) {
|
||||||
|
// Minimal safe error handling
|
||||||
|
safeSetState(() => {
|
||||||
|
setErrorDetails('A critical error occurred');
|
||||||
|
setShowErrorModal(true);
|
||||||
|
});
|
||||||
|
// Force exit after 3 seconds if error handler itself fails
|
||||||
|
setTimeout(() => {
|
||||||
|
if (isMounted.current) {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setErrorDetails(errorMessage);
|
|
||||||
setShowErrorModal(true);
|
|
||||||
|
|
||||||
// Clear any existing timeout
|
|
||||||
if (errorTimeoutRef.current) {
|
|
||||||
clearTimeout(errorTimeoutRef.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-exit after 5 seconds if user doesn't dismiss
|
|
||||||
errorTimeoutRef.current = setTimeout(() => {
|
|
||||||
handleErrorExit();
|
|
||||||
}, 5000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleErrorExit = () => {
|
const handleErrorExit = () => {
|
||||||
if (errorTimeoutRef.current) {
|
try {
|
||||||
clearTimeout(errorTimeoutRef.current);
|
// Early return if component is unmounted
|
||||||
errorTimeoutRef.current = null;
|
if (!isMounted.current) {
|
||||||
|
logger.warn('[AndroidVideoPlayer] Component unmounted, skipping error exit');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorTimeoutRef.current) {
|
||||||
|
clearTimeout(errorTimeoutRef.current);
|
||||||
|
errorTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use safeSetState to prevent crashes on iOS when component is unmounted
|
||||||
|
safeSetState(() => {
|
||||||
|
setShowErrorModal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add small delay before closing to ensure modal state is updated
|
||||||
|
setTimeout(() => {
|
||||||
|
if (isMounted.current) {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
} catch (exitError) {
|
||||||
|
logger.error('[AndroidVideoPlayer] Error in handleErrorExit:', exitError);
|
||||||
|
// Force close as last resort
|
||||||
|
if (isMounted.current) {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setShowErrorModal(false);
|
|
||||||
handleClose();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBuffer = (data: any) => {
|
const onBuffer = (data: any) => {
|
||||||
|
|
@ -1323,12 +1380,15 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Error Modal */}
|
{/* Error Modal */}
|
||||||
<Modal
|
{isMounted.current && (
|
||||||
visible={showErrorModal}
|
<Modal
|
||||||
transparent
|
visible={showErrorModal}
|
||||||
animationType="fade"
|
transparent
|
||||||
onRequestClose={handleErrorExit}
|
animationType="fade"
|
||||||
>
|
onRequestClose={handleErrorExit}
|
||||||
|
supportedOrientations={['landscape', 'portrait']}
|
||||||
|
statusBarTranslucent={true}
|
||||||
|
>
|
||||||
<View style={{
|
<View style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
|
@ -1414,7 +1474,8 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
}}>This dialog will auto-close in 5 seconds</Text>
|
}}>This dialog will auto-close in 5 seconds</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ export interface AppSettings {
|
||||||
enableScraperUrlValidation: boolean; // Enable/disable URL validation for scrapers
|
enableScraperUrlValidation: boolean; // Enable/disable URL validation for scrapers
|
||||||
streamDisplayMode: 'separate' | 'grouped'; // How to display streaming links - separately by provider or grouped under one name
|
streamDisplayMode: 'separate' | 'grouped'; // How to display streaming links - separately by provider or grouped under one name
|
||||||
streamSortMode: 'scraper-then-quality' | 'quality-then-scraper'; // How to sort streams - by scraper first or quality first
|
streamSortMode: 'scraper-then-quality' | 'quality-then-scraper'; // How to sort streams - by scraper first or quality first
|
||||||
|
showScraperLogos: boolean; // Show/hide scraper logos next to streaming links
|
||||||
// Quality filtering settings
|
// Quality filtering settings
|
||||||
excludedQualities: string[]; // Array of quality strings to exclude (e.g., ['2160p', '4K', '1080p', '720p'])
|
excludedQualities: string[]; // Array of quality strings to exclude (e.g., ['2160p', '4K', '1080p', '720p'])
|
||||||
}
|
}
|
||||||
|
|
@ -70,6 +71,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
||||||
enableScraperUrlValidation: true, // Enable URL validation by default
|
enableScraperUrlValidation: true, // Enable URL validation by default
|
||||||
streamDisplayMode: 'separate', // Default to separate display by provider
|
streamDisplayMode: 'separate', // Default to separate display by provider
|
||||||
streamSortMode: 'scraper-then-quality', // Default to current behavior (scraper first, then quality)
|
streamSortMode: 'scraper-then-quality', // Default to current behavior (scraper first, then quality)
|
||||||
|
showScraperLogos: true, // Show scraper logos by default
|
||||||
// Quality filtering defaults
|
// Quality filtering defaults
|
||||||
excludedQualities: [], // No qualities excluded by default
|
excludedQualities: [], // No qualities excluded by default
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -824,6 +824,22 @@ const PluginsScreen: React.FC = () => {
|
||||||
disabled={!settings.enableLocalScrapers || settings.streamDisplayMode !== 'grouped'}
|
disabled={!settings.enableLocalScrapers || settings.streamDisplayMode !== 'grouped'}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.settingRow}>
|
||||||
|
<View style={styles.settingInfo}>
|
||||||
|
<Text style={[styles.settingTitle, !settings.enableLocalScrapers && styles.disabledText]}>Show Scraper Logos</Text>
|
||||||
|
<Text style={[styles.settingDescription, !settings.enableLocalScrapers && styles.disabledText]}>
|
||||||
|
Display scraper logos next to streaming links on the streams screen.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Switch
|
||||||
|
value={settings.showScraperLogos && settings.enableLocalScrapers}
|
||||||
|
onValueChange={(value) => updateSetting('showScraperLogos', value)}
|
||||||
|
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||||
|
thumbColor={settings.showScraperLogos && settings.enableLocalScrapers ? colors.white : '#f4f3f4'}
|
||||||
|
disabled={!settings.enableLocalScrapers}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Quality Filtering */}
|
{/* Quality Filtering */}
|
||||||
|
|
|
||||||
|
|
@ -62,13 +62,14 @@ const scraperLogoCache = new Map<string, string>();
|
||||||
let scraperLogoCachePromise: Promise<void> | null = null;
|
let scraperLogoCachePromise: Promise<void> | null = null;
|
||||||
|
|
||||||
// Extracted Components
|
// Extracted Components
|
||||||
const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, theme }: {
|
const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, theme, showLogos }: {
|
||||||
stream: Stream;
|
stream: Stream;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
index: number;
|
index: number;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
statusMessage?: string;
|
statusMessage?: string;
|
||||||
theme: any;
|
theme: any;
|
||||||
|
showLogos?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const styles = React.useMemo(() => createStyles(theme.colors), [theme.colors]);
|
const styles = React.useMemo(() => createStyles(theme.colors), [theme.colors]);
|
||||||
|
|
||||||
|
|
@ -157,7 +158,7 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
{/* Scraper Logo */}
|
{/* Scraper Logo */}
|
||||||
{scraperLogo && (
|
{showLogos && scraperLogo && (
|
||||||
<View style={styles.scraperLogoContainer}>
|
<View style={styles.scraperLogoContainer}>
|
||||||
<Image
|
<Image
|
||||||
source={{ uri: scraperLogo }}
|
source={{ uri: scraperLogo }}
|
||||||
|
|
@ -1276,9 +1277,10 @@ export const StreamsScreen = () => {
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
statusMessage={undefined}
|
statusMessage={undefined}
|
||||||
theme={currentTheme}
|
theme={currentTheme}
|
||||||
|
showLogos={settings.showScraperLogos}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, [handleStreamPress, currentTheme]);
|
}, [handleStreamPress, currentTheme, settings.showScraperLogos]);
|
||||||
|
|
||||||
const renderSectionHeader = useCallback(({ section }: { section: { title: string; addonId: string } }) => {
|
const renderSectionHeader = useCallback(({ section }: { section: { title: string; addonId: string } }) => {
|
||||||
const isProviderLoading = loadingProviders[section.addonId];
|
const isProviderLoading = loadingProviders[section.addonId];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue