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 httpHeaders: Map<String, String>? = null
// Hardware decoding setting (default: false = software decoding)
var useHardwareDecoding: Boolean = false
// Decoder mode setting: 'auto', 'sw', 'hw', 'hw+' (default: auto)
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)
private var hasLoadEventFired: Boolean = false
@ -94,22 +97,47 @@ class MPVView @JvmOverloads constructor(
private fun initOptions() {
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("opengl-es", "yes")
val hwdecValue = if (useHardwareDecoding) "mediacodec,mediacodec-copy" else "no"
Log.d(TAG, "Hardware decoding: $useHardwareDecoding, hwdec value: $hwdecValue")
// Decoder mode mapping (same as mpvKt)
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-codecs", "h264,hevc,mpeg4,mpeg2video,vp8,vp9,av1")
// Note: Not setting hwdec-codecs explicitly - let mpv use defaults
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("ao", "audiotrack,opensles")
MPVLib.setOptionString("demuxer-max-bytes", "67108864")
MPVLib.setOptionString("demuxer-max-back-bytes", "33554432")
// Limit demuxer cache based on Android version (like mpvKt)
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-secs", "30")
@ -126,9 +154,6 @@ class MPVView @JvmOverloads constructor(
MPVLib.setOptionString("demuxer-seekable-cache", "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-visibility", "yes")
MPVLib.setOptionString("sub-font-size", "48")

View file

@ -181,8 +181,13 @@ class MpvPlayerViewManager(
}
}
@ReactProp(name = "useHardwareDecoding")
fun setUseHardwareDecoding(view: MPVView, useHardwareDecoding: Boolean) {
view.useHardwareDecoding = useHardwareDecoding
@ReactProp(name = "decoderMode")
fun setDecoderMode(view: MPVView, decoderMode: String?) {
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
targetSdkVersion = 35
castFrameworkVersion = "22.1.0"
ndkVersion = "29.0.14206865" // Required for libmpv AAR built with NDK r29
}
repositories {
google()

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

View file

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

View file

@ -25,7 +25,8 @@ export interface MpvPlayerProps {
onEnd?: () => void;
onError?: (error: { error: string }) => 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) => {
@ -104,7 +105,8 @@ const MpvPlayer = forwardRef<MpvPlayerRef, MpvPlayerProps>((props, ref) => {
onEnd={handleEnd}
onError={handleError}
onTracksChanged={handleTracksChanged}
useHardwareDecoding={props.useHardwareDecoding ?? false}
decoderMode={props.decoderMode ?? 'auto'}
gpuMode={props.gpuMode ?? 'gpu'}
/>
);
});

View file

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

View file

@ -335,51 +335,139 @@ const PlayerSettingsScreen: React.FC = () => {
</View>
</View>
{/* Hardware Decoding for Android Internal Player */}
{/* Decoder Mode for Android Internal Player */}
{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.settingIconContainer,
{ backgroundColor: 'rgba(255,255,255,0.1)' }
]}>
<MaterialIcons
name="memory"
size={20}
color={currentTheme.colors.primary}
/>
<>
<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="memory"
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 style={styles.settingText}>
<Text
style={[
styles.settingTitle,
{ color: currentTheme.colors.text },
]}
>
Hardware Decoding
</Text>
<Text
style={[
styles.settingDescription,
{ color: currentTheme.colors.textMuted },
]}
>
Use GPU for video decoding. May improve performance but can cause issues on some devices.
</Text>
<View style={styles.optionButtonsRow}>
{([
{ id: 'auto', label: 'Auto', desc: 'Best balance' },
{ id: 'sw', label: 'SW', desc: 'Software' },
{ id: 'hw', label: 'HW', desc: 'Hardware' },
{ id: 'hw+', label: 'HW+', desc: 'Full HW' },
] as const).map((option) => (
<TouchableOpacity
key={option.id}
onPress={() => {
updateSetting('decoderMode', option.id);
openAlert(
'Restart Required',
'Please restart the app for the decoder change to take effect.'
);
}}
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>
<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>
{/* 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 */}
@ -532,6 +620,28 @@ const styles = StyleSheet.create({
checkIcon: {
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;