mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 16:51:57 +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 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")
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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 */}
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -335,9 +335,10 @@ 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.settingItem, styles.settingItemBorder, { borderTopColor: 'rgba(255,255,255,0.08)', borderTopWidth: 1 }]}>
|
||||||
<View style={styles.settingContent}>
|
<View style={styles.settingContent}>
|
||||||
<View style={[
|
<View style={[
|
||||||
styles.settingIconContainer,
|
styles.settingIconContainer,
|
||||||
|
|
@ -356,7 +357,7 @@ const PlayerSettingsScreen: React.FC = () => {
|
||||||
{ color: currentTheme.colors.text },
|
{ color: currentTheme.colors.text },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
Hardware Decoding
|
Decoder Mode
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
|
|
@ -364,22 +365,109 @@ const PlayerSettingsScreen: React.FC = () => {
|
||||||
{ color: currentTheme.colors.textMuted },
|
{ color: currentTheme.colors.textMuted },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
Use GPU for video decoding. May improve performance but can cause issues on some devices.
|
How video is decoded. Auto is recommended for best balance.
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Switch
|
</View>
|
||||||
value={settings.useHardwareDecoding}
|
<View style={styles.optionButtonsRow}>
|
||||||
onValueChange={(value) => {
|
{([
|
||||||
updateSetting('useHardwareDecoding', value);
|
{ 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(
|
openAlert(
|
||||||
'Restart Required',
|
'Restart Required',
|
||||||
'Please restart the app for the decoding change to take effect.'
|
'Please restart the app for the decoder change to take effect.'
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
thumbColor={settings.useHardwareDecoding ? currentTheme.colors.primary : undefined}
|
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>
|
||||||
|
|
||||||
|
{/* 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>
|
||||||
|
<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>
|
||||||
|
<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;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue