added hwdec options to choose from

This commit is contained in:
tapframe 2025-12-27 17:50:09 +05:30
parent 063f8a8c1b
commit 579b0a77b3
9 changed files with 213 additions and 64 deletions

View file

@ -24,8 +24,11 @@ class MPVView @JvmOverloads constructor(
private var surface: Surface? = null private var surface: Surface? = null
private var httpHeaders: Map<String, String>? = null private var httpHeaders: Map<String, String>? = null
// Hardware decoding setting (default: false = software decoding) // Decoder mode setting: 'auto', 'sw', 'hw', 'hw+' (default: auto)
var useHardwareDecoding: Boolean = false var decoderMode: String = "auto"
// GPU mode setting: 'gpu', 'gpu-next' (default: gpu)
var gpuMode: String = "gpu"
// Flag to track if onLoad has been fired (prevents multiple fires for HLS streams) // Flag to track if onLoad has been fired (prevents multiple fires for HLS streams)
private var hasLoadEventFired: Boolean = false private var hasLoadEventFired: Boolean = false
@ -94,22 +97,47 @@ class MPVView @JvmOverloads constructor(
private fun initOptions() { private fun initOptions() {
MPVLib.setOptionString("profile", "fast") MPVLib.setOptionString("profile", "fast")
MPVLib.setOptionString("vo", "gpu")
// GPU rendering mode (gpu or gpu-next)
MPVLib.setOptionString("vo", gpuMode)
MPVLib.setOptionString("gpu-context", "android") MPVLib.setOptionString("gpu-context", "android")
MPVLib.setOptionString("opengl-es", "yes") MPVLib.setOptionString("opengl-es", "yes")
val hwdecValue = if (useHardwareDecoding) "mediacodec,mediacodec-copy" else "no" // Decoder mode mapping (same as mpvKt)
Log.d(TAG, "Hardware decoding: $useHardwareDecoding, hwdec value: $hwdecValue") val hwdecValue = when (decoderMode) {
"auto" -> "auto-copy" // Best balance: HW decode, copy to CPU for filters
"sw" -> "no" // Software decoding only
"hw" -> "mediacodec-copy" // HW decode with copy (safer)
"hw+" -> "mediacodec" // Full HW decode (fastest, may have issues)
else -> "auto-copy"
}
Log.d(TAG, "Decoder mode: $decoderMode, hwdec value: $hwdecValue, GPU mode: $gpuMode")
MPVLib.setOptionString("hwdec", hwdecValue) MPVLib.setOptionString("hwdec", hwdecValue)
MPVLib.setOptionString("hwdec-codecs", "h264,hevc,mpeg4,mpeg2video,vp8,vp9,av1") // Note: Not setting hwdec-codecs explicitly - let mpv use defaults
MPVLib.setOptionString("target-colorspace-hint", "yes") MPVLib.setOptionString("target-colorspace-hint", "yes")
// HDR and Dolby Vision support
// target-prim: Signal target display primaries (auto = passthrough when display supports)
MPVLib.setOptionString("target-prim", "auto")
// target-trc: Signal target transfer characteristics (auto = passthrough when display supports)
MPVLib.setOptionString("target-trc", "auto")
// tone-mapping: How to handle HDR/DV content on SDR displays (auto = best automatic choice)
MPVLib.setOptionString("tone-mapping", "auto")
// hdr-compute-peak: Compute peak brightness for better tone mapping
MPVLib.setOptionString("hdr-compute-peak", "auto")
// Allow DV Profile 5 (HEVC with RPU) to be decoded by hardware decoder
MPVLib.setOptionString("vd-lavc-o", "strict=-2")
// Workaround for https://github.com/mpv-player/mpv/issues/14651
MPVLib.setOptionString("vd-lavc-film-grain", "cpu") MPVLib.setOptionString("vd-lavc-film-grain", "cpu")
MPVLib.setOptionString("ao", "audiotrack,opensles") MPVLib.setOptionString("ao", "audiotrack,opensles")
MPVLib.setOptionString("demuxer-max-bytes", "67108864") // Limit demuxer cache based on Android version (like mpvKt)
MPVLib.setOptionString("demuxer-max-back-bytes", "33554432") val cacheMegs = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O_MR1) 64 else 32
MPVLib.setOptionString("demuxer-max-bytes", "${cacheMegs * 1024 * 1024}")
MPVLib.setOptionString("demuxer-max-back-bytes", "${cacheMegs * 1024 * 1024}")
MPVLib.setOptionString("cache", "yes") MPVLib.setOptionString("cache", "yes")
MPVLib.setOptionString("cache-secs", "30") MPVLib.setOptionString("cache-secs", "30")
@ -126,9 +154,6 @@ class MPVView @JvmOverloads constructor(
MPVLib.setOptionString("demuxer-seekable-cache", "yes") MPVLib.setOptionString("demuxer-seekable-cache", "yes")
MPVLib.setOptionString("force-seekable", "yes") MPVLib.setOptionString("force-seekable", "yes")
MPVLib.setOptionString("demuxer-lavf-probesize", "10000000")
MPVLib.setOptionString("demuxer-lavf-analyzeduration", "10")
MPVLib.setOptionString("sub-auto", "fuzzy") MPVLib.setOptionString("sub-auto", "fuzzy")
MPVLib.setOptionString("sub-visibility", "yes") MPVLib.setOptionString("sub-visibility", "yes")
MPVLib.setOptionString("sub-font-size", "48") MPVLib.setOptionString("sub-font-size", "48")

View file

@ -181,8 +181,13 @@ class MpvPlayerViewManager(
} }
} }
@ReactProp(name = "useHardwareDecoding") @ReactProp(name = "decoderMode")
fun setUseHardwareDecoding(view: MPVView, useHardwareDecoding: Boolean) { fun setDecoderMode(view: MPVView, decoderMode: String?) {
view.useHardwareDecoding = useHardwareDecoding view.decoderMode = decoderMode ?: "auto"
}
@ReactProp(name = "gpuMode")
fun setGpuMode(view: MPVView, gpuMode: String?) {
view.gpuMode = gpuMode ?: "gpu"
} }
} }

View file

@ -7,6 +7,7 @@ buildscript {
compileSdkVersion = 35 compileSdkVersion = 35
targetSdkVersion = 35 targetSdkVersion = 35
castFrameworkVersion = "22.1.0" castFrameworkVersion = "22.1.0"
ndkVersion = "29.0.14206865" // Required for libmpv AAR built with NDK r29
} }
repositories { repositories {
google() google()

@ -1 +1 @@
Subproject commit 8c4778b5aad441bb0449a7f9b3d6d827fd3d6a2a Subproject commit db3b10e64353349d0d72619ca7d779829e36fe4d

View file

@ -568,7 +568,8 @@ const AndroidVideoPlayer: React.FC = () => {
onPinchGestureEvent={() => { }} onPinchGestureEvent={() => { }}
onPinchHandlerStateChange={() => { }} onPinchHandlerStateChange={() => { }}
screenDimensions={playerState.screenDimensions} screenDimensions={playerState.screenDimensions}
useHardwareDecoding={settings.useHardwareDecoding} decoderMode={settings.decoderMode}
gpuMode={settings.gpuMode}
/> />
)} )}

View file

@ -25,7 +25,8 @@ export interface MpvPlayerProps {
onEnd?: () => void; onEnd?: () => void;
onError?: (error: { error: string }) => void; onError?: (error: { error: string }) => void;
onTracksChanged?: (data: { audioTracks: any[]; subtitleTracks: any[] }) => void; onTracksChanged?: (data: { audioTracks: any[]; subtitleTracks: any[] }) => void;
useHardwareDecoding?: boolean; decoderMode?: 'auto' | 'sw' | 'hw' | 'hw+';
gpuMode?: 'gpu' | 'gpu-next';
} }
const MpvPlayer = forwardRef<MpvPlayerRef, MpvPlayerProps>((props, ref) => { const MpvPlayer = forwardRef<MpvPlayerRef, MpvPlayerProps>((props, ref) => {
@ -104,7 +105,8 @@ const MpvPlayer = forwardRef<MpvPlayerRef, MpvPlayerProps>((props, ref) => {
onEnd={handleEnd} onEnd={handleEnd}
onError={handleError} onError={handleError}
onTracksChanged={handleTracksChanged} onTracksChanged={handleTracksChanged}
useHardwareDecoding={props.useHardwareDecoding ?? false} decoderMode={props.decoderMode ?? 'auto'}
gpuMode={props.gpuMode ?? 'gpu'}
/> />
); );
}); });

View file

@ -32,7 +32,8 @@ interface VideoSurfaceProps {
onPinchHandlerStateChange: any; onPinchHandlerStateChange: any;
screenDimensions: { width: number, height: number }; screenDimensions: { width: number, height: number };
onTracksChanged?: (data: { audioTracks: any[]; subtitleTracks: any[] }) => void; onTracksChanged?: (data: { audioTracks: any[]; subtitleTracks: any[] }) => void;
useHardwareDecoding?: boolean; decoderMode?: 'auto' | 'sw' | 'hw' | 'hw+';
gpuMode?: 'gpu' | 'gpu-next';
} }
export const VideoSurface: React.FC<VideoSurfaceProps> = ({ export const VideoSurface: React.FC<VideoSurfaceProps> = ({
@ -56,7 +57,8 @@ export const VideoSurface: React.FC<VideoSurfaceProps> = ({
onPinchHandlerStateChange, onPinchHandlerStateChange,
screenDimensions, screenDimensions,
onTracksChanged, onTracksChanged,
useHardwareDecoding, decoderMode,
gpuMode,
}) => { }) => {
// Use the actual stream URL // Use the actual stream URL
const streamUrl = currentStreamUrl || processedStreamUrl; const streamUrl = currentStreamUrl || processedStreamUrl;
@ -115,7 +117,8 @@ export const VideoSurface: React.FC<VideoSurfaceProps> = ({
onEnd={handleEnd} onEnd={handleEnd}
onError={handleError} onError={handleError}
onTracksChanged={onTracksChanged} onTracksChanged={onTracksChanged}
useHardwareDecoding={useHardwareDecoding} decoderMode={decoderMode}
gpuMode={gpuMode}
/> />
{/* Gesture overlay - transparent, on top of the player */} {/* Gesture overlay - transparent, on top of the player */}

View file

@ -89,7 +89,8 @@ export interface AppSettings {
enableStreamsBackdrop: boolean; // Enable blurred backdrop background on StreamsScreen mobile enableStreamsBackdrop: boolean; // Enable blurred backdrop background on StreamsScreen mobile
useExternalPlayerForDownloads: boolean; // Enable/disable external player for downloaded content useExternalPlayerForDownloads: boolean; // Enable/disable external player for downloaded content
// Android MPV player settings // Android MPV player settings
useHardwareDecoding: boolean; // Enable hardware decoding for MPV player on Android (default: false for software decoding) decoderMode: 'auto' | 'sw' | 'hw' | 'hw+'; // Decoder mode: auto (auto-copy), sw (software), hw (mediacodec-copy), hw+ (mediacodec)
gpuMode: 'gpu' | 'gpu-next'; // GPU rendering mode: gpu (standard) or gpu-next (advanced HDR/color)
} }
export const DEFAULT_SETTINGS: AppSettings = { export const DEFAULT_SETTINGS: AppSettings = {
@ -152,7 +153,8 @@ export const DEFAULT_SETTINGS: AppSettings = {
streamCacheTTL: 60 * 60 * 1000, // Default: 1 hour in milliseconds streamCacheTTL: 60 * 60 * 1000, // Default: 1 hour in milliseconds
enableStreamsBackdrop: true, // Enable by default (new behavior) enableStreamsBackdrop: true, // Enable by default (new behavior)
// Android MPV player settings // Android MPV player settings
useHardwareDecoding: false, // Default to software decoding (more compatible) decoderMode: 'auto', // Default to auto (best compatibility and performance)
gpuMode: 'gpu', // Default to gpu (gpu-next for advanced HDR)
}; };
const SETTINGS_STORAGE_KEY = 'app_settings'; const SETTINGS_STORAGE_KEY = 'app_settings';

View file

@ -335,51 +335,139 @@ const PlayerSettingsScreen: React.FC = () => {
</View> </View>
</View> </View>
{/* Hardware Decoding for Android Internal Player */} {/* Decoder Mode for Android Internal Player */}
{Platform.OS === 'android' && !settings.useExternalPlayer && ( {Platform.OS === 'android' && !settings.useExternalPlayer && (
<View style={[styles.settingItem, styles.settingItemBorder, { borderTopColor: 'rgba(255,255,255,0.08)' }]}> <>
<View style={styles.settingContent}> <View style={[styles.settingItem, styles.settingItemBorder, { borderTopColor: 'rgba(255,255,255,0.08)', borderTopWidth: 1 }]}>
<View style={[ <View style={styles.settingContent}>
styles.settingIconContainer, <View style={[
{ backgroundColor: 'rgba(255,255,255,0.1)' } styles.settingIconContainer,
]}> { backgroundColor: 'rgba(255,255,255,0.1)' }
<MaterialIcons ]}>
name="memory" <MaterialIcons
size={20} name="memory"
color={currentTheme.colors.primary} size={20}
/> color={currentTheme.colors.primary}
/>
</View>
<View style={styles.settingText}>
<Text
style={[
styles.settingTitle,
{ color: currentTheme.colors.text },
]}
>
Decoder Mode
</Text>
<Text
style={[
styles.settingDescription,
{ color: currentTheme.colors.textMuted },
]}
>
How video is decoded. Auto is recommended for best balance.
</Text>
</View>
</View> </View>
<View style={styles.settingText}> <View style={styles.optionButtonsRow}>
<Text {([
style={[ { id: 'auto', label: 'Auto', desc: 'Best balance' },
styles.settingTitle, { id: 'sw', label: 'SW', desc: 'Software' },
{ color: currentTheme.colors.text }, { id: 'hw', label: 'HW', desc: 'Hardware' },
]} { id: 'hw+', label: 'HW+', desc: 'Full HW' },
> ] as const).map((option) => (
Hardware Decoding <TouchableOpacity
</Text> key={option.id}
<Text onPress={() => {
style={[ updateSetting('decoderMode', option.id);
styles.settingDescription, openAlert(
{ color: currentTheme.colors.textMuted }, 'Restart Required',
]} 'Please restart the app for the decoder change to take effect.'
> );
Use GPU for video decoding. May improve performance but can cause issues on some devices. }}
</Text> style={[
styles.optionButton,
settings.decoderMode === option.id && { backgroundColor: currentTheme.colors.primary },
]}
>
<Text
style={[
styles.optionButtonText,
{ color: settings.decoderMode === option.id ? '#fff' : currentTheme.colors.text },
]}
>
{option.label}
</Text>
</TouchableOpacity>
))}
</View> </View>
<Switch
value={settings.useHardwareDecoding}
onValueChange={(value) => {
updateSetting('useHardwareDecoding', value);
openAlert(
'Restart Required',
'Please restart the app for the decoding change to take effect.'
);
}}
thumbColor={settings.useHardwareDecoding ? currentTheme.colors.primary : undefined}
/>
</View> </View>
</View>
{/* GPU Mode for Android Internal Player */}
<View style={[styles.settingItem, styles.settingItemBorder, { borderTopColor: 'rgba(255,255,255,0.08)', borderTopWidth: 1 }]}>
<View style={styles.settingContent}>
<View style={[
styles.settingIconContainer,
{ backgroundColor: 'rgba(255,255,255,0.1)' }
]}>
<MaterialIcons
name="videocam"
size={20}
color={currentTheme.colors.primary}
/>
</View>
<View style={styles.settingText}>
<Text
style={[
styles.settingTitle,
{ color: currentTheme.colors.text },
]}
>
GPU Rendering
</Text>
<Text
style={[
styles.settingDescription,
{ color: currentTheme.colors.textMuted },
]}
>
GPU-Next offers better HDR and color management.
</Text>
</View>
</View>
<View style={styles.optionButtonsRow}>
{([
{ id: 'gpu', label: 'GPU', desc: 'Standard' },
{ id: 'gpu-next', label: 'GPU-Next', desc: 'Advanced' },
] as const).map((option) => (
<TouchableOpacity
key={option.id}
onPress={() => {
updateSetting('gpuMode', option.id);
openAlert(
'Restart Required',
'Please restart the app for the GPU mode change to take effect.'
);
}}
style={[
styles.optionButton,
styles.optionButtonWide,
settings.gpuMode === option.id && { backgroundColor: currentTheme.colors.primary },
]}
>
<Text
style={[
styles.optionButtonText,
{ color: settings.gpuMode === option.id ? '#fff' : currentTheme.colors.text },
]}
>
{option.label}
</Text>
</TouchableOpacity>
))}
</View>
</View>
</>
)} )}
{/* External Player for Downloads */} {/* External Player for Downloads */}
@ -532,6 +620,28 @@ const styles = StyleSheet.create({
checkIcon: { checkIcon: {
marginLeft: 16, marginLeft: 16,
}, },
optionButtonsRow: {
flexDirection: 'row',
marginTop: 12,
paddingHorizontal: 52,
gap: 8,
},
optionButton: {
flex: 1,
paddingVertical: 10,
paddingHorizontal: 12,
borderRadius: 8,
backgroundColor: 'rgba(255,255,255,0.1)',
alignItems: 'center',
justifyContent: 'center',
},
optionButtonWide: {
flex: 1.5,
},
optionButtonText: {
fontSize: 13,
fontWeight: '600',
},
}); });
export default PlayerSettingsScreen; export default PlayerSettingsScreen;