mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
added hwdec options to choose from
This commit is contained in:
parent
063f8a8c1b
commit
579b0a77b3
9 changed files with 213 additions and 64 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -568,7 +568,8 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
onPinchGestureEvent={() => { }}
|
||||
onPinchHandlerStateChange={() => { }}
|
||||
screenDimensions={playerState.screenDimensions}
|
||||
useHardwareDecoding={settings.useHardwareDecoding}
|
||||
decoderMode={settings.decoderMode}
|
||||
gpuMode={settings.gpuMode}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 */}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue