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 ebaf8d0..c94bd0f 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 @@ -23,6 +23,9 @@ class MPVView @JvmOverloads constructor( private var isPaused: Boolean = true private var surface: Surface? = null private var httpHeaders: Map? = null + + // Hardware decoding setting (default: false = software decoding) + var useHardwareDecoding: Boolean = false // Event listener for React Native var onLoadCallback: ((duration: Double, width: Int, height: Int) -> Unit)? = null @@ -94,10 +97,11 @@ class MPVView @JvmOverloads constructor( MPVLib.setOptionString("opengl-es", "yes") // Hardware decoding configuration - // NOTE: On emulator, mediacodec can cause freezes due to slow GPU translation - // Using 'no' for software decoding which is more reliable on emulator - // For real devices, use 'mediacodec-copy' for hardware acceleration - MPVLib.setOptionString("hwdec", "no") + // 'mediacodec-copy' for hardware acceleration (GPU decoding, copies frames to CPU) + // 'no' for software decoding (more compatible, especially on emulators) + val hwdecValue = if (useHardwareDecoding) "mediacodec-copy" else "no" + Log.d(TAG, "Hardware decoding: $useHardwareDecoding, hwdec value: $hwdecValue") + MPVLib.setOptionString("hwdec", hwdecValue) MPVLib.setOptionString("hwdec-codecs", "all") // Audio output 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 27d4852..c50f928 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 @@ -180,4 +180,9 @@ class MpvPlayerViewManager( view.setHeaders(null) } } + + @ReactProp(name = "useHardwareDecoding") + fun setUseHardwareDecoding(view: MPVView, useHardwareDecoding: Boolean) { + view.useHardwareDecoding = useHardwareDecoding + } } diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index 568a9c4..57588ca 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -24,6 +24,7 @@ import { useNextEpisode } from './android/hooks/useNextEpisode'; import { useTraktAutosync } from '../../hooks/useTraktAutosync'; import { useMetadata } from '../../hooks/useMetadata'; import { usePlayerGestureControls } from '../../hooks/usePlayerGestureControls'; +import { useSettings } from '../../hooks/useSettings'; // Shared Components import { GestureControls, PauseOverlay, SpeedActivatedOverlay } from './components'; @@ -69,6 +70,7 @@ const AndroidVideoPlayer: React.FC = () => { const playerState = usePlayerState(); const modals = usePlayerModals(); const speedControl = useSpeedControl(); + const { settings } = useSettings(); const videoRef = useRef(null); const mpvPlayerRef = useRef(null); @@ -550,6 +552,7 @@ const AndroidVideoPlayer: React.FC = () => { onPinchGestureEvent={() => { }} onPinchHandlerStateChange={() => { }} screenDimensions={playerState.screenDimensions} + useHardwareDecoding={settings.useHardwareDecoding} /> {/* Custom Subtitles for addon subtitles */} diff --git a/src/components/player/android/MpvPlayer.tsx b/src/components/player/android/MpvPlayer.tsx index 9aec1d5..04c7b08 100644 --- a/src/components/player/android/MpvPlayer.tsx +++ b/src/components/player/android/MpvPlayer.tsx @@ -25,6 +25,7 @@ export interface MpvPlayerProps { onEnd?: () => void; onError?: (error: { error: string }) => void; onTracksChanged?: (data: { audioTracks: any[]; subtitleTracks: any[] }) => void; + useHardwareDecoding?: boolean; } const MpvPlayer = forwardRef((props, ref) => { @@ -103,6 +104,7 @@ const MpvPlayer = forwardRef((props, ref) => { onEnd={handleEnd} onError={handleError} onTracksChanged={handleTracksChanged} + useHardwareDecoding={props.useHardwareDecoding ?? false} /> ); }); diff --git a/src/components/player/android/components/VideoSurface.tsx b/src/components/player/android/components/VideoSurface.tsx index c6d0d86..e4e2d50 100644 --- a/src/components/player/android/components/VideoSurface.tsx +++ b/src/components/player/android/components/VideoSurface.tsx @@ -32,6 +32,7 @@ interface VideoSurfaceProps { onPinchHandlerStateChange: any; screenDimensions: { width: number, height: number }; onTracksChanged?: (data: { audioTracks: any[]; subtitleTracks: any[] }) => void; + useHardwareDecoding?: boolean; } export const VideoSurface: React.FC = ({ @@ -55,6 +56,7 @@ export const VideoSurface: React.FC = ({ onPinchHandlerStateChange, screenDimensions, onTracksChanged, + useHardwareDecoding, }) => { // Use the actual stream URL const streamUrl = currentStreamUrl || processedStreamUrl; @@ -113,6 +115,7 @@ export const VideoSurface: React.FC = ({ onEnd={handleEnd} onError={handleError} onTracksChanged={onTracksChanged} + useHardwareDecoding={useHardwareDecoding} /> {/* Gesture overlay - transparent, on top of the player */} diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index d893569..21e9e81 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -88,6 +88,8 @@ export interface AppSettings { streamCacheTTL: number; // Stream cache duration in milliseconds (default: 1 hour) enableStreamsBackdrop: boolean; // Enable blurred backdrop background on StreamsScreen mobile useExternalPlayerForDownloads: boolean; // Enable/disable external player for downloaded content + // Android MPV player settings + useHardwareDecoding: boolean; // Enable hardware decoding for MPV player on Android (default: false for software decoding) } export const DEFAULT_SETTINGS: AppSettings = { @@ -149,6 +151,8 @@ export const DEFAULT_SETTINGS: AppSettings = { openMetadataScreenWhenCacheDisabled: true, // Default to StreamsScreen when cache disabled streamCacheTTL: 60 * 60 * 1000, // Default: 1 hour in milliseconds enableStreamsBackdrop: true, // Enable by default (new behavior) + // Android MPV player settings + useHardwareDecoding: false, // Default to software decoding (more compatible) }; const SETTINGS_STORAGE_KEY = 'app_settings'; diff --git a/src/screens/PlayerSettingsScreen.tsx b/src/screens/PlayerSettingsScreen.tsx index a3e57cb..577da4d 100644 --- a/src/screens/PlayerSettingsScreen.tsx +++ b/src/screens/PlayerSettingsScreen.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { View, Text, @@ -14,6 +14,7 @@ import { useNavigation } from '@react-navigation/native'; import { useSettings, AppSettings } from '../hooks/useSettings'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import { useTheme } from '../contexts/ThemeContext'; +import CustomAlert from '../components/CustomAlert'; const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0; @@ -95,6 +96,17 @@ const PlayerSettingsScreen: React.FC = () => { const { currentTheme } = useTheme(); const navigation = useNavigation(); + // CustomAlert state + const [alertVisible, setAlertVisible] = useState(false); + const [alertTitle, setAlertTitle] = useState(''); + const [alertMessage, setAlertMessage] = useState(''); + + const openAlert = (title: string, message: string) => { + setAlertTitle(title); + setAlertMessage(message); + setAlertVisible(true); + }; + const playerOptions = [ { id: 'internal', @@ -323,6 +335,53 @@ const PlayerSettingsScreen: React.FC = () => { + {/* Hardware Decoding for Android Internal Player */} + {Platform.OS === 'android' && !settings.useExternalPlayer && ( + + + + + + + + Hardware Decoding + + + Use GPU for video decoding. May improve performance but can cause issues on some devices. + + + { + updateSetting('useHardwareDecoding', value); + openAlert( + 'Restart Required', + 'Please restart the app for the decoding change to take effect.' + ); + }} + thumbColor={settings.useHardwareDecoding ? currentTheme.colors.primary : undefined} + /> + + + )} + {/* External Player for Downloads */} {((Platform.OS === 'android' && settings.useExternalPlayer) || (Platform.OS === 'ios' && settings.preferredPlayer !== 'internal')) && ( @@ -367,6 +426,13 @@ const PlayerSettingsScreen: React.FC = () => { + + setAlertVisible(false)} + /> ); };