fix: resolve shader persistence loop, icons, and preset memory; add FSR/SSim shaders and clean UI

This commit is contained in:
paregi12 2026-02-07 15:24:52 +05:30
parent f6b13c27c9
commit cb1bc64daa
6 changed files with 116 additions and 43 deletions

Binary file not shown.

View file

@ -85,7 +85,7 @@ const AndroidVideoPlayer: React.FC = () => {
const playerState = usePlayerState();
const modals = usePlayerModals();
const speedControl = useSpeedControl();
const { settings } = useSettings();
const { settings, updateSetting } = useSettings();
const videoRef = useRef<any>(null);
const mpvPlayerRef = useRef<MpvPlayerRef>(null);
@ -151,7 +151,7 @@ const AndroidVideoPlayer: React.FC = () => {
// Shader / Video Enhancement State
const [showEnhancementModal, setShowEnhancementModal] = useState(false);
const [shaderMode, setShaderModeState] = useState<ShaderMode>('none');
const [shaderMode, setShaderModeState] = useState<ShaderMode>(settings.defaultShaderMode || 'none');
const [glslShaders, setGlslShaders] = useState<string>('');
// Color Profile State
@ -172,13 +172,22 @@ const AndroidVideoPlayer: React.FC = () => {
setCurrentVideoSettings(visualEnhancementService.getCurrentSettings());
}, []);
// Initialize shader config from persisted setting
useEffect(() => {
if (settings.defaultShaderMode && settings.defaultShaderMode !== 'none') {
const config = shaderService.getShaderConfig(settings.defaultShaderMode, settings.shaderProfile as any || 'MID-END');
setGlslShaders(config);
}
}, [settings.defaultShaderMode, settings.shaderProfile]);
const setShaderMode = useCallback((mode: ShaderMode) => {
setShaderModeState(mode);
updateSetting('defaultShaderMode', mode); // Persist selection
const config = shaderService.getShaderConfig(mode, settings.shaderProfile as any || 'MID-END');
setGlslShaders(config);
// Don't close modal here, let user close it
// setShowEnhancementModal(false);
}, [settings.shaderProfile]);
}, [settings.shaderProfile, updateSetting]);
const handleSetProfile = useCallback(async (profile: string) => {
await visualEnhancementService.setProfile(profile);

View file

@ -54,15 +54,15 @@ const ShaderTab = ({ currentMode, setMode }: { currentMode: string, setMode: (m:
const { settings } = useSettings();
const selectedCategory = (settings?.shaderProfile || 'MID-END') as ShaderCategory;
const animeModes = Object.keys(SHADER_PROFILES[selectedCategory]);
const cinemaModes = Object.keys(SHADER_PROFILES['CINEMA']);
const animeModes = SHADER_PROFILES[selectedCategory] ? Object.keys(SHADER_PROFILES[selectedCategory]) : [];
const cinemaModes = SHADER_PROFILES['CINEMA'] ? Object.keys(SHADER_PROFILES['CINEMA']) : [];
const getModeDescription = (name: string) => {
if (name.includes('Mode A')) return 'Best for high-quality sources.';
if (name.includes('Mode B')) return 'Soft restore for noisy videos.';
if (name.includes('Mode C')) return 'Balanced restore and upscale.';
if (name.includes('FSR')) return 'Sharp upscaling for live-action.';
if (name.includes('SSim')) return 'Natural reconstruction.';
if (name.includes('SSimSuperRes')) return 'Natural sharpness and anti-ringing.';
return '';
};
@ -96,20 +96,24 @@ const ShaderTab = ({ currentMode, setMode }: { currentMode: string, setMode: (m:
/>
))}
<View style={styles.sectionHeader}>
<Ionicons name="film-outline" size={16} color="rgba(255,255,255,0.4)" style={{ marginRight: 8 }} />
<Text style={styles.sectionTitle}>CINEMA</Text>
</View>
{cinemaModes.length > 0 && (
<>
<View style={styles.sectionHeader}>
<Ionicons name="film-outline" size={16} color="rgba(255,255,255,0.4)" style={{ marginRight: 8 }} />
<Text style={styles.sectionTitle}>CINEMA</Text>
</View>
{cinemaModes.map((mode) => (
<PresetItem
key={mode}
label={mode}
description={getModeDescription(mode)}
isSelected={currentMode === mode}
onPress={() => setMode(mode)}
/>
))}
{cinemaModes.map((mode) => (
<PresetItem
key={mode}
label={mode}
description={getModeDescription(mode)}
isSelected={currentMode === mode}
onPress={() => setMode(mode)}
/>
))}
</>
)}
</ScrollView>
);
};

View file

@ -118,6 +118,7 @@ export interface AppSettings {
// Upscaler settings
enableShaders: boolean; // Enable/disable real-time upscalers
shaderProfile: 'MID-END' | 'HIGH-END'; // Hardware profile for upscalers
defaultShaderMode: string; // Persisted shader preset (e.g. 'Anime4K: Mode A')
}
export const DEFAULT_SETTINGS: AppSettings = {
@ -209,6 +210,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
// Upscaler defaults
enableShaders: false,
shaderProfile: 'MID-END',
defaultShaderMode: 'none',
};
const SETTINGS_STORAGE_KEY = 'app_settings';

View file

@ -107,6 +107,7 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
if (isMounted.current) {
setIsDownloadingEnhancement(false);
if (success) {
shaderService.setInitialized(true); // Force update service state
setIsEnhancementDownloaded(true);
toastService.success(t('settings.enhancement_download_success', { defaultValue: 'Enhancement assets installed!' }));
} else {
@ -338,14 +339,14 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
)}
</SettingsCard>
{/* Experimental Settings Section - AnymeX Style */}
<SettingsCard title={t('settings.sections.experimental', { defaultValue: 'Experimental Settings' })} isTablet={isTablet}>
{/* Shaders Section */}
<SettingsCard title="Shaders" isTablet={isTablet}>
{/* Enable Shaders Toggle */}
<SettingItem
title={t('settings.items.enable_shaders', { defaultValue: 'Enable Shaders' })}
description={t('settings.items.enable_shaders_desc', { defaultValue: 'Apply real-time shaders (Anime4K/FSR) to the player.' })}
icon="color-filter"
icon="eye"
renderControl={() => (
<CustomSwitch
value={settings?.enableShaders ?? false}
@ -361,7 +362,7 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
<SettingItem
title={t('settings.items.shader_profile', { defaultValue: 'Shader Profile' })}
description={t('settings.items.shader_profile_desc', { defaultValue: 'Choose based on your device performance.' })}
icon="hardware-chip"
icon="activity"
renderControl={() => (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ color: currentTheme.colors.primary, marginRight: 8, fontWeight: '700' }}>
@ -447,9 +448,28 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
)}
{isEnhancementDownloaded && (
<Text style={{ color: 'rgba(255, 255, 255, 0.6)', fontSize: 12 }}>
All required shader files are ready. You can switch profiles in the video player menu.
</Text>
<View style={{ marginTop: 8 }}>
<Text style={{ color: 'rgba(255, 255, 255, 0.6)', fontSize: 12, marginBottom: 12 }}>
All required shader files are ready. You can switch profiles in the video player menu.
</Text>
<TouchableOpacity
style={{
backgroundColor: 'rgba(255, 255, 255, 0.05)',
borderRadius: 8,
paddingVertical: 10,
alignItems: 'center',
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.1)'
}}
onPress={async () => {
await shaderService.clearShaders();
setIsEnhancementDownloaded(false);
toastService.success('Shader assets cleared');
}}
>
<Text style={{ color: 'rgba(255, 255, 255, 0.6)', fontWeight: '600', fontSize: 13 }}>Reset Assets</Text>
</TouchableOpacity>
</View>
)}
</View>
</>

View file

@ -12,6 +12,7 @@ const ESSENTIAL_SHADERS = [
'Anime4K_Restore_CNN_M.glsl',
'Anime4K_Upscale_CNN_x2_M.glsl',
'FSR.glsl',
'SSimSuperRes.glsl',
];
// Exact profiles from AnymeX
@ -105,7 +106,7 @@ export const SHADER_PROFILES = {
},
"CINEMA": {
'FidelityFX Super Resolution': ['FSR.glsl'],
'SSimSuperRes': ['SSimSuperRes.glsl'],
'SSimSuperRes (Natural)': ['SSimSuperRes.glsl'],
}
};
@ -127,23 +128,16 @@ class ShaderService {
}
/**
* Check if all shaders are already extracted and available
* Check if shaders are available
*/
async checkAvailability(): Promise<boolean> {
try {
const dirInfo = await FileSystem.getInfoAsync(this.shaderDir);
if (!dirInfo.exists) return false;
// Check key files
const results = await Promise.all(
ESSENTIAL_SHADERS.map(async (filename) => {
const path = `${this.shaderDir}${filename}`;
const info = await FileSystem.getInfoAsync(path);
return info.exists;
})
);
this.initialized = results.every(v => v === true);
const files = await FileSystem.readDirectoryAsync(this.shaderDir);
// As long as we have some files, consider it initialized
this.initialized = files.length >= 3;
return this.initialized;
} catch {
return false;
@ -157,6 +151,27 @@ class ShaderService {
await this.checkAvailability();
}
// Manually update initialization state (e.g. after download)
setInitialized(value: boolean) {
this.initialized = value;
}
/**
* Delete extracted shaders (troubleshooting)
*/
async clearShaders(): Promise<void> {
try {
const dirInfo = await FileSystem.getInfoAsync(this.shaderDir);
if (dirInfo.exists) {
await FileSystem.deleteAsync(this.shaderDir, { idempotent: true });
this.initialized = false;
logger.info('[ShaderService] Shaders cleared');
}
} catch (e) {
logger.error('[ShaderService] Failed to clear shaders', e);
}
}
/**
* Extract shaders from bundled assets
*/
@ -176,12 +191,19 @@ class ShaderService {
if (onProgress) onProgress(0.3);
// 2. Read Zip
const zipContent = await FileSystem.readAsStringAsync(asset.localUri || asset.uri, { encoding: FileSystem.EncodingType.Base64 });
// Use copyAsync to avoid loading entire binary into memory as string
const tempZip = `${FileSystem.cacheDirectory}shaders_temp.zip`;
await FileSystem.copyAsync({
from: asset.localUri || asset.uri,
to: tempZip
});
// 2. Read Zip (Small file so Base64 is okay, but using safer approach)
const zipContent = await FileSystem.readAsStringAsync(tempZip, { encoding: FileSystem.EncodingType.Base64 });
const zip = await JSZip.loadAsync(zipContent, { base64: true });
if (onProgress) onProgress(0.6);
logger.info('[ShaderService] Extracting shader files...');
logger.info(`[ShaderService] Extracting ${Object.keys(zip.files).length} files...`);
// 3. Extract Files
const files = Object.keys(zip.files);
@ -189,10 +211,13 @@ class ShaderService {
for (const filename of files) {
if (!zip.files[filename].dir) {
const content = await zip.files[filename].async('base64');
const content = await zip.files[filename].async('uint8array');
// For binary data, writing as base64 is safest in expo-file-system
const base64 = this.uint8ToBase64(content);
await FileSystem.writeAsStringAsync(
`${this.shaderDir}${filename}`,
content,
base64,
{ encoding: FileSystem.EncodingType.Base64 }
);
}
@ -200,6 +225,9 @@ class ShaderService {
if (onProgress) onProgress(0.6 + (extractedCount / files.length) * 0.4);
}
// Clean up temp zip
await FileSystem.deleteAsync(tempZip, { idempotent: true });
this.initialized = true;
logger.info('[ShaderService] Shaders installed successfully');
return true;
@ -209,6 +237,16 @@ class ShaderService {
}
}
// Helper for binary conversion
private uint8ToBase64(arr: Uint8Array): string {
let binary = '';
const len = arr.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(arr[i]);
}
return btoa(binary);
}
/**
* Get the MPV configuration string for the selected profile
*/