mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
fix: implement native upscaler bridge, equalizer methods, and restore centered player UI
This commit is contained in:
parent
9f6d456bf3
commit
2394f5d158
7 changed files with 197 additions and 83 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -299,14 +299,6 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
|||
>
|
||||
{/* Progress slider with native iOS slider */}
|
||||
<View style={styles.sliderContainer}>
|
||||
<View style={[styles.timeDisplay, { paddingHorizontal: 14 }]}>
|
||||
<View style={styles.timeContainer}>
|
||||
<Text style={styles.duration}>{formatTime(previewTime)}</Text>
|
||||
</View>
|
||||
<View style={styles.timeContainer}>
|
||||
<Text style={styles.duration}>{formatTime(duration)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Slider
|
||||
style={{
|
||||
width: '100%',
|
||||
|
|
@ -336,6 +328,14 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
|||
thumbTintColor={Platform.OS === 'android' ? currentTheme.colors.white : undefined}
|
||||
tapToSeek={Platform.OS === 'ios'}
|
||||
/>
|
||||
<View style={[styles.timeDisplay, { paddingHorizontal: 14 }]}>
|
||||
<View style={styles.timeContainer}>
|
||||
<Text style={styles.duration}>{formatTime(previewTime)}</Text>
|
||||
</View>
|
||||
<View style={styles.timeContainer}>
|
||||
<Text style={styles.duration}>{formatTime(duration)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Controls Overlay */}
|
||||
|
|
@ -393,6 +393,21 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
|||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{/* Video Enhancement Button (Top Access) */}
|
||||
{playerBackend === 'MPV' && setShowEnhancementModal && (
|
||||
<TouchableOpacity
|
||||
style={{ padding: 8 }}
|
||||
onPress={() => setShowEnhancementModal(true)}
|
||||
>
|
||||
<Ionicons
|
||||
name="sparkles-outline"
|
||||
size={closeIconSize}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
<TouchableOpacity style={styles.closeButton} onPress={handleClose}>
|
||||
<Ionicons name="close" size={closeIconSize} color="white" />
|
||||
</TouchableOpacity>
|
||||
|
|
@ -592,88 +607,72 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
|||
pointerEvents="box-none"
|
||||
>
|
||||
<View style={styles.bottomControls} pointerEvents="box-none">
|
||||
{/* Center Buttons Container with split layout */}
|
||||
{/* Center Buttons Container with rounded background - wraps all buttons */}
|
||||
<View style={styles.centerControlsContainer} pointerEvents="box-none">
|
||||
{/* Left Group */}
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
{/* Aspect Ratio Button */}
|
||||
<TouchableOpacity style={styles.iconButton} onPress={cycleAspectRatio}>
|
||||
<Ionicons name="expand-outline" size={24} color="white" />
|
||||
</TouchableOpacity>
|
||||
{/* Aspect Ratio Button */}
|
||||
<TouchableOpacity style={styles.iconButton} onPress={cycleAspectRatio}>
|
||||
<Ionicons name="expand-outline" size={24} color="white" />
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Video Enhancement Button (MPV Only) */}
|
||||
{playerBackend === 'MPV' && setShowEnhancementModal && (
|
||||
<TouchableOpacity
|
||||
style={styles.iconButton}
|
||||
onPress={() => setShowEnhancementModal(true)}
|
||||
>
|
||||
<Ionicons name="sparkles-outline" size={24} color="white" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{/* Subtitle Button */}
|
||||
<TouchableOpacity
|
||||
style={styles.iconButton}
|
||||
onPress={() => setShowSubtitleModal(!isSubtitleModalOpen)}
|
||||
>
|
||||
<Ionicons name="text" size={24} color="white" />
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Subtitle Button */}
|
||||
{/* Playback Speed Button */}
|
||||
<TouchableOpacity style={styles.iconButton} onPress={() => setShowSpeedModal(true)}>
|
||||
<Ionicons name="speedometer-outline" size={24} color="white" />
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Audio Button */}
|
||||
<TouchableOpacity
|
||||
style={styles.iconButton}
|
||||
onPress={() => setShowAudioModal(true)}
|
||||
disabled={ksAudioTracks.length <= 1}
|
||||
>
|
||||
<Ionicons
|
||||
name="musical-notes-outline"
|
||||
size={24}
|
||||
color={ksAudioTracks.length <= 1 ? 'grey' : 'white'}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Change Source Button */}
|
||||
{setShowSourcesModal && (
|
||||
<TouchableOpacity
|
||||
style={styles.iconButton}
|
||||
onPress={() => setShowSubtitleModal(!isSubtitleModalOpen)}
|
||||
onPress={() => setShowSourcesModal(true)}
|
||||
>
|
||||
<Ionicons name="text" size={24} color="white" />
|
||||
<Ionicons name="cloud-outline" size={24} color="white" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{/* Playback Speed Button */}
|
||||
<TouchableOpacity style={styles.iconButton} onPress={() => setShowSpeedModal(true)}>
|
||||
<Ionicons name="speedometer-outline" size={24} color="white" />
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Audio Button */}
|
||||
{/* Submit Intro Button */}
|
||||
{season !== undefined && episode !== undefined && settings.introSubmitEnabled && settings.introDbApiKey && (
|
||||
<TouchableOpacity
|
||||
style={styles.iconButton}
|
||||
onPress={() => setShowAudioModal(true)}
|
||||
disabled={ksAudioTracks.length <= 1}
|
||||
onPress={handleIntroPress}
|
||||
>
|
||||
<Ionicons
|
||||
name="musical-notes-outline"
|
||||
name="flag-outline"
|
||||
size={24}
|
||||
color={ksAudioTracks.length <= 1 ? 'grey' : 'white'}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{/* Submit Intro Button */}
|
||||
{season !== undefined && episode !== undefined && settings.introSubmitEnabled && settings.introDbApiKey && (
|
||||
<TouchableOpacity
|
||||
style={styles.iconButton}
|
||||
onPress={handleIntroPress}
|
||||
>
|
||||
<Ionicons
|
||||
name="flag-outline"
|
||||
size={24}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Right Group */}
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
{/* Change Source Button */}
|
||||
{setShowSourcesModal && (
|
||||
<TouchableOpacity
|
||||
style={styles.iconButton}
|
||||
onPress={() => setShowSourcesModal(true)}
|
||||
>
|
||||
<Ionicons name="cloud-outline" size={24} color="white" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{/* Episodes Button */}
|
||||
{setShowEpisodesModal && (
|
||||
<TouchableOpacity
|
||||
style={styles.iconButton}
|
||||
onPress={() => setShowEpisodesModal(true)}
|
||||
>
|
||||
<Ionicons name="list" size={24} color="white" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
{/* Episodes Button */}
|
||||
{setShowEpisodesModal && (
|
||||
<TouchableOpacity
|
||||
style={styles.iconButton}
|
||||
onPress={() => setShowEpisodesModal(true)}
|
||||
>
|
||||
<Ionicons name="list" size={24} color="white" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
<PostHogOptController
|
||||
enabled={analyticsEnabled}
|
||||
|
|
|
|||
|
|
@ -225,16 +225,23 @@ class ShaderService {
|
|||
for (const cat of Object.keys(SHADER_PROFILES)) {
|
||||
const list = SHADER_PROFILES[cat as ShaderCategory] as Record<string, string[]>;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue