From 2394f5d158c3f81453ae20ed786c9b2636b6dd6b Mon Sep 17 00:00:00 2001 From: paregi12 Date: Sat, 7 Feb 2026 08:32:27 +0530 Subject: [PATCH] fix: implement native upscaler bridge, equalizer methods, and restore centered player UI --- .../main/java/com/nuvio/app/mpv/MPVView.kt | 74 +++++++++ .../com/nuvio/app/mpv/MpvPlayerViewManager.kt | 32 ++++ src/components/player/AndroidVideoPlayer.tsx | 1 + .../player/controls/PlayerControls.tsx | 147 +++++++++--------- src/components/player/utils/playerStyles.ts | 11 +- src/navigation/AppNavigator.tsx | 2 +- src/services/shaderService.ts | 13 +- 7 files changed, 197 insertions(+), 83 deletions(-) diff --git a/android/app/src/main/java/com/nuvio/app/mpv/MPVView.kt b/android/app/src/main/java/com/nuvio/app/mpv/MPVView.kt index f2f9cd4f..bb2a4e4a 100644 --- a/android/app/src/main/java/com/nuvio/app/mpv/MPVView.kt +++ b/android/app/src/main/java/com/nuvio/app/mpv/MPVView.kt @@ -33,9 +33,19 @@ class MPVView @JvmOverloads constructor( // GPU mode setting: 'gpu', 'gpu-next' (default: gpu) var gpuMode: String = "gpu" + // GLSL shaders setting (for upscalers) + private var glslShadersVal: String? = null + // Flag to track if onLoad has been fired (prevents multiple fires for HLS streams) private var hasLoadEventFired: Boolean = false + // Video Equalizer state + private var brightnessVal: Int = 0 + private var contrastVal: Int = 0 + private var saturationVal: Int = 0 + private var gammaVal: Int = 0 + private var hueVal: Int = 0 + // Event listener for React Native var onLoadCallback: ((duration: Double, width: Int, height: Int) -> Unit)? = null var onProgressCallback: ((position: Double, duration: Double) -> Unit)? = null @@ -78,6 +88,7 @@ class MPVView @JvmOverloads constructor( MPVLib.addObserver(this) MPVLib.setPropertyString("android-surface-size", "${width}x${height}") observeProperties() + applyPostInitSettings() isMpvInitialized = true // If a data source was set before surface was ready, load it now @@ -477,6 +488,69 @@ class MPVView @JvmOverloads constructor( } } + // Video Equalizer Methods + + fun setBrightness(value: Int) { + brightnessVal = value + if (isMpvInitialized) { + Log.d(TAG, "Setting brightness: $value") + MPVLib.setPropertyDouble("brightness", value.toDouble()) + } + } + + fun setContrast(value: Int) { + contrastVal = value + if (isMpvInitialized) { + Log.d(TAG, "Setting contrast: $value") + MPVLib.setPropertyDouble("contrast", value.toDouble()) + } + } + + fun setSaturation(value: Int) { + saturationVal = value + if (isMpvInitialized) { + Log.d(TAG, "Setting saturation: $value") + MPVLib.setPropertyDouble("saturation", value.toDouble()) + } + } + + fun setGamma(value: Int) { + gammaVal = value + if (isMpvInitialized) { + Log.d(TAG, "Setting gamma: $value") + MPVLib.setPropertyDouble("gamma", value.toDouble()) + } + } + + fun setHue(value: Int) { + hueVal = value + if (isMpvInitialized) { + Log.d(TAG, "Setting hue: $value") + MPVLib.setPropertyDouble("hue", value.toDouble()) + } + } + + fun setGlslShaders(shaders: String?) { + glslShadersVal = shaders + if (isMpvInitialized) { + Log.d(TAG, "Setting glsl-shaders: $shaders") + MPVLib.setPropertyString("glsl-shaders", shaders ?: "") + } + } + + private fun applyPostInitSettings() { + Log.d(TAG, "Applying post-init settings: B=$brightnessVal, C=$contrastVal, S=$saturationVal, G=$gammaVal, H=$hueVal, Shaders=$glslShadersVal") + MPVLib.setPropertyDouble("brightness", brightnessVal.toDouble()) + MPVLib.setPropertyDouble("contrast", contrastVal.toDouble()) + MPVLib.setPropertyDouble("saturation", saturationVal.toDouble()) + MPVLib.setPropertyDouble("gamma", gammaVal.toDouble()) + MPVLib.setPropertyDouble("hue", hueVal.toDouble()) + + glslShadersVal?.let { + MPVLib.setPropertyString("glsl-shaders", it) + } + } + // MPVLib.EventObserver implementation override fun eventProperty(property: String) { diff --git a/android/app/src/main/java/com/nuvio/app/mpv/MpvPlayerViewManager.kt b/android/app/src/main/java/com/nuvio/app/mpv/MpvPlayerViewManager.kt index 5054e04c..0f9ff291 100644 --- a/android/app/src/main/java/com/nuvio/app/mpv/MpvPlayerViewManager.kt +++ b/android/app/src/main/java/com/nuvio/app/mpv/MpvPlayerViewManager.kt @@ -191,6 +191,11 @@ class MpvPlayerViewManager( view.gpuMode = gpuMode ?: "gpu" } + @ReactProp(name = "glslShaders") + fun setGlslShaders(view: MPVView, glslShaders: String?) { + view.setGlslShaders(glslShaders) + } + // Subtitle Styling Props @ReactProp(name = "subtitleSize", defaultInt = 48) @@ -238,4 +243,31 @@ class MpvPlayerViewManager( fun setSubtitleAlignment(view: MPVView, align: String?) { view.setSubtitleAlignment(align ?: "center") } + + // Video Equalizer Props + + @ReactProp(name = "brightness", defaultInt = 0) + fun setBrightness(view: MPVView, brightness: Int) { + view.setBrightness(brightness) + } + + @ReactProp(name = "contrast", defaultInt = 0) + fun setContrast(view: MPVView, contrast: Int) { + view.setContrast(contrast) + } + + @ReactProp(name = "saturation", defaultInt = 0) + fun setSaturation(view: MPVView, saturation: Int) { + view.setSaturation(saturation) + } + + @ReactProp(name = "gamma", defaultInt = 0) + fun setGamma(view: MPVView, gamma: Int) { + view.setGamma(gamma) + } + + @ReactProp(name = "hue", defaultInt = 0) + fun setHue(view: MPVView, hue: Int) { + view.setHue(hue) + } } diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index 79236244..ac990d9e 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -37,6 +37,7 @@ import { SubtitleSyncModal } from './modals/SubtitleSyncModal'; import SpeedModal from './modals/SpeedModal'; import { SubmitIntroModal } from './modals/SubmitIntroModal'; import { SourcesModal } from './modals/SourcesModal'; +import { ResolutionModal } from './modals/ResolutionModal'; import { EpisodesModal } from './modals/EpisodesModal'; import { EpisodeStreamsModal } from './modals/EpisodeStreamsModal'; import { ErrorModal } from './modals/ErrorModal'; diff --git a/src/components/player/controls/PlayerControls.tsx b/src/components/player/controls/PlayerControls.tsx index db3ee359..4530d84c 100644 --- a/src/components/player/controls/PlayerControls.tsx +++ b/src/components/player/controls/PlayerControls.tsx @@ -299,14 +299,6 @@ export const PlayerControls: React.FC = ({ > {/* Progress slider with native iOS slider */} - - - {formatTime(previewTime)} - - - {formatTime(duration)} - - = ({ thumbTintColor={Platform.OS === 'android' ? currentTheme.colors.white : undefined} tapToSeek={Platform.OS === 'ios'} /> + + + {formatTime(previewTime)} + + + {formatTime(duration)} + + {/* Controls Overlay */} @@ -393,6 +393,21 @@ export const PlayerControls: React.FC = ({ /> )} + + {/* Video Enhancement Button (Top Access) */} + {playerBackend === 'MPV' && setShowEnhancementModal && ( + setShowEnhancementModal(true)} + > + + + )} + @@ -592,88 +607,72 @@ export const PlayerControls: React.FC = ({ pointerEvents="box-none" > - {/* Center Buttons Container with split layout */} + {/* Center Buttons Container with rounded background - wraps all buttons */} - {/* Left Group */} - - {/* Aspect Ratio Button */} - - - + {/* Aspect Ratio Button */} + + + - {/* Video Enhancement Button (MPV Only) */} - {playerBackend === 'MPV' && setShowEnhancementModal && ( - setShowEnhancementModal(true)} - > - - - )} + {/* Subtitle Button */} + setShowSubtitleModal(!isSubtitleModalOpen)} + > + + - {/* Subtitle Button */} + {/* Playback Speed Button */} + setShowSpeedModal(true)}> + + + + {/* Audio Button */} + setShowAudioModal(true)} + disabled={ksAudioTracks.length <= 1} + > + + + + {/* Change Source Button */} + {setShowSourcesModal && ( setShowSubtitleModal(!isSubtitleModalOpen)} + onPress={() => setShowSourcesModal(true)} > - + + )} - {/* Playback Speed Button */} - setShowSpeedModal(true)}> - - - - {/* Audio Button */} + {/* Submit Intro Button */} + {season !== undefined && episode !== undefined && settings.introSubmitEnabled && settings.introDbApiKey && ( setShowAudioModal(true)} - disabled={ksAudioTracks.length <= 1} + onPress={handleIntroPress} > + )} - {/* Submit Intro Button */} - {season !== undefined && episode !== undefined && settings.introSubmitEnabled && settings.introDbApiKey && ( - - - - )} - - - {/* Right Group */} - - {/* Change Source Button */} - {setShowSourcesModal && ( - setShowSourcesModal(true)} - > - - - )} - - {/* Episodes Button */} - {setShowEpisodesModal && ( - setShowEpisodesModal(true)} - > - - - )} - + {/* Episodes Button */} + {setShowEpisodesModal && ( + setShowEpisodesModal(true)} + > + + + )} diff --git a/src/components/player/utils/playerStyles.ts b/src/components/player/utils/playerStyles.ts index 791e2cc2..35dd6f7c 100644 --- a/src/components/player/utils/playerStyles.ts +++ b/src/components/player/utils/playerStyles.ts @@ -352,8 +352,8 @@ export const styles = StyleSheet.create({ justifyContent: 'space-between', width: '100%', paddingHorizontal: 4, - marginTop: 0, - marginBottom: 2, + marginTop: 4, + marginBottom: 8, }, duration: { color: 'white', @@ -399,15 +399,16 @@ export const styles = StyleSheet.create({ centerControlsContainer: { flexDirection: 'row', alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 6, + justifyContent: 'center', + gap: 2, + paddingHorizontal: 4, paddingVertical: 2, marginTop: 12, backgroundColor: 'rgba(0, 0, 0, 0.5)', borderRadius: 24, borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.2)', - width: '100%', + alignSelf: 'center', zIndex: 1002, }, modalOverlay: { diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index 2d6017aa..ee906dbd 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -1924,10 +1924,10 @@ const ConditionalPostHogProvider: React.FC<{ children: React.ReactNode }> = ({ c apiKey="phc_sk6THCtV3thEAn6cTaA9kL2cHuKDBnlYiSL40ywdS6C" options={{ host: "https://us.i.posthog.com", + autocapture: analyticsEnabled, // Start opted out if analytics is disabled defaultOptIn: analyticsEnabled, }} - autocapture={analyticsEnabled} > ; if (list[profileName]) { - return list[profileName].map(name => `${this.shaderDir}${name}`).join(':'); + const cleanDir = this.shaderDir.replace('file://', ''); + return list[profileName].map(name => `${cleanDir}${name}`).join(':'); } } return ""; } // Map filenames to full local paths and join with ':' (MPV separator) - return shaderNames - .map(name => `${this.shaderDir}${name}`) + // IMPORTANT: Strip 'file://' prefix for MPV native path compatibility + const cleanDir = this.shaderDir.replace('file://', ''); + + const config = shaderNames + .map(name => `${cleanDir}${name}`) .join(':'); + + logger.info(`[ShaderService] Generated config for ${profileName}:`, config); + return config; } }