changed mpv hw to try "mediacode" first

This commit is contained in:
tapframe 2025-12-25 20:31:14 +05:30
parent 7e7804b6d4
commit 0b4db84f30
3 changed files with 117 additions and 121 deletions

View file

@ -93,95 +93,70 @@ class MPVView @JvmOverloads constructor(
}
private fun initOptions() {
// Mobile-optimized profile
MPVLib.setOptionString("profile", "fast")
MPVLib.setOptionString("vo", "gpu")
MPVLib.setOptionString("gpu-context", "android")
MPVLib.setOptionString("opengl-es", "yes")
// Hardware decoding configuration
// '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"
val hwdecValue = if (useHardwareDecoding) "mediacodec,mediacodec-copy" else "no"
Log.d(TAG, "Hardware decoding: $useHardwareDecoding, hwdec value: $hwdecValue")
MPVLib.setOptionString("hwdec", hwdecValue)
MPVLib.setOptionString("hwdec-codecs", "all")
MPVLib.setOptionString("hwdec-codecs", "h264,hevc,mpeg4,mpeg2video,vp8,vp9,av1")
MPVLib.setOptionString("target-colorspace-hint", "yes")
MPVLib.setOptionString("vd-lavc-film-grain", "cpu")
// Audio output
MPVLib.setOptionString("ao", "audiotrack,opensles")
// Network caching for streaming
MPVLib.setOptionString("demuxer-max-bytes", "67108864") // 64MB
MPVLib.setOptionString("demuxer-max-back-bytes", "33554432") // 32MB
MPVLib.setOptionString("demuxer-max-bytes", "67108864")
MPVLib.setOptionString("demuxer-max-back-bytes", "33554432")
MPVLib.setOptionString("cache", "yes")
MPVLib.setOptionString("cache-secs", "30")
// Network options
MPVLib.setOptionString("network-timeout", "60") // 60 second timeout
// CRITICAL: Disable youtube-dl/yt-dlp hook
// The ytdl_hook incorrectly tries to parse HLS/direct URLs through youtube-dl
// which fails on Android since yt-dlp is not available, causing playback failure
MPVLib.setOptionString("network-timeout", "60")
MPVLib.setOptionString("ytdl", "no")
// CRITICAL: HTTP headers MUST be set as options before init()
// Apply headers if they were set before surface initialization
applyHttpHeadersAsOptions()
// FFmpeg HTTP protocol options for better compatibility
MPVLib.setOptionString("tls-verify", "no") // Disable TLS cert verification
MPVLib.setOptionString("http-reconnect", "yes") // Auto-reconnect on network issues
MPVLib.setOptionString("stream-reconnect", "yes") // Reconnect if stream drops
MPVLib.setOptionString("tls-verify", "no")
MPVLib.setOptionString("http-reconnect", "yes")
MPVLib.setOptionString("stream-reconnect", "yes")
// CRITICAL: HLS demuxer options for proper VOD stream handling
// Without these, HLS streams may be treated as live and start from the end
// Note: Multiple lavf options separated by comma
MPVLib.setOptionString("demuxer-lavf-o", "live_start_index=0,prefer_x_start=1,http_persistent=0")
MPVLib.setOptionString("demuxer-seekable-cache", "yes") // Allow seeking in cached content
MPVLib.setOptionString("force-seekable", "yes") // Force stream to be seekable
MPVLib.setOptionString("demuxer-seekable-cache", "yes")
MPVLib.setOptionString("force-seekable", "yes")
// Increase probe/analyze duration to help detect full HLS duration
MPVLib.setOptionString("demuxer-lavf-probesize", "10000000") // 10MB probe size
MPVLib.setOptionString("demuxer-lavf-analyzeduration", "10") // 10 seconds analyze
MPVLib.setOptionString("demuxer-lavf-probesize", "10000000")
MPVLib.setOptionString("demuxer-lavf-analyzeduration", "10")
// Subtitle configuration - CRITICAL for Android
MPVLib.setOptionString("sub-auto", "fuzzy") // Auto-load subtitles
MPVLib.setOptionString("sub-visibility", "yes") // Make subtitles visible by default
MPVLib.setOptionString("sub-font-size", "48") // Larger font size for mobile readability
MPVLib.setOptionString("sub-pos", "95") // Position at bottom (0-100, 100 = very bottom)
MPVLib.setOptionString("sub-color", "#FFFFFFFF") // White color
MPVLib.setOptionString("sub-border-size", "3") // Thicker border for readability
MPVLib.setOptionString("sub-border-color", "#FF000000") // Black border
MPVLib.setOptionString("sub-shadow-offset", "2") // Add shadow for better visibility
MPVLib.setOptionString("sub-shadow-color", "#80000000") // Semi-transparent black shadow
MPVLib.setOptionString("sub-auto", "fuzzy")
MPVLib.setOptionString("sub-visibility", "yes")
MPVLib.setOptionString("sub-font-size", "48")
MPVLib.setOptionString("sub-pos", "95")
MPVLib.setOptionString("sub-color", "#FFFFFFFF")
MPVLib.setOptionString("sub-border-size", "3")
MPVLib.setOptionString("sub-border-color", "#FF000000")
MPVLib.setOptionString("sub-shadow-offset", "2")
MPVLib.setOptionString("sub-shadow-color", "#80000000")
// Font configuration - point to Android system fonts for all language support
MPVLib.setOptionString("osd-fonts-dir", "/system/fonts")
MPVLib.setOptionString("sub-fonts-dir", "/system/fonts")
MPVLib.setOptionString("sub-font", "Roboto") // Default fallback font
// Allow embedded fonts in ASS/SSA but fallback to system fonts
MPVLib.setOptionString("sub-font", "Roboto")
MPVLib.setOptionString("embeddedfonts", "yes")
// Language/encoding support for various subtitle formats
MPVLib.setOptionString("sub-codepage", "auto") // Auto-detect encoding (supports UTF-8, Latin, CJK, etc.)
MPVLib.setOptionString("sub-codepage", "auto")
MPVLib.setOptionString("osc", "no") // Disable on screen controller
MPVLib.setOptionString("osc", "no")
MPVLib.setOptionString("osd-level", "1")
// Critical for subtitle rendering on Android GPU
// blend-subtitles=no lets the GPU renderer handle subtitle overlay properly
MPVLib.setOptionString("blend-subtitles", "no")
MPVLib.setOptionString("sub-use-margins", "no")
// Use 'scale' to allow ASS styling but with our scale and font overrides
// This preserves styled subtitles while having font fallbacks
MPVLib.setOptionString("sub-ass-override", "scale")
MPVLib.setOptionString("sub-scale", "1.0")
MPVLib.setOptionString("sub-fix-timing", "yes") // Fix timing for SRT subtitles
MPVLib.setOptionString("sub-fix-timing", "yes")
// Force subtitle rendering
MPVLib.setOptionString("sid", "auto") // Auto-select subtitle track
MPVLib.setOptionString("sid", "auto")
// Disable terminal/input
MPVLib.setOptionString("terminal", "no")
MPVLib.setOptionString("input-default-bindings", "no")
}

View file

@ -76,6 +76,7 @@ const AndroidVideoPlayer: React.FC = () => {
const videoRef = useRef<any>(null);
const mpvPlayerRef = useRef<MpvPlayerRef>(null);
const pinchRef = useRef(null);
const tracksHook = usePlayerTracks();
const [currentStreamUrl, setCurrentStreamUrl] = useState<string>(uri);
@ -86,6 +87,9 @@ const AndroidVideoPlayer: React.FC = () => {
const [currentStreamProvider, setCurrentStreamProvider] = useState(streamProvider);
const [currentStreamName, setCurrentStreamName] = useState(streamName);
// State to force unmount VideoSurface during stream transitions
const [isTransitioningStream, setIsTransitioningStream] = useState(false);
// Subtitle addon state
const [availableSubtitles, setAvailableSubtitles] = useState<WyzieSubtitle[]>([]);
const [isLoadingSubtitleList, setIsLoadingSubtitleList] = useState(false);
@ -329,10 +333,14 @@ const AndroidVideoPlayer: React.FC = () => {
modals.setShowSourcesModal(false);
playerState.setPaused(true);
// Unmount VideoSurface first to ensure MPV is fully destroyed
setIsTransitioningStream(true);
const newQuality = newStream.quality || newStream.title?.match(/(\d+)p/)?.[0];
const newProvider = newStream.addonName || newStream.name || newStream.addon || 'Unknown';
const newStreamName = newStream.name || newStream.title || 'Unknown';
// Wait for unmount to complete, then navigate
setTimeout(() => {
(navigation as any).replace('PlayerAndroid', {
...route.params,
@ -343,19 +351,24 @@ const AndroidVideoPlayer: React.FC = () => {
headers: newStream.headers,
availableStreams: availableStreams
});
}, 100);
}, 300);
};
const handleEpisodeStreamSelect = async (stream: any) => {
if (!modals.selectedEpisodeForStreams) return;
modals.setShowEpisodeStreamsModal(false);
playerState.setPaused(true);
// Unmount VideoSurface first to ensure MPV is fully destroyed
setIsTransitioningStream(true);
const ep = modals.selectedEpisodeForStreams;
const newQuality = stream.quality || (stream.title?.match(/(\d+)p/)?.[0]);
const newProvider = stream.addonName || stream.name || stream.addon || 'Unknown';
const newStreamName = stream.name || stream.title || 'Unknown Stream';
// Wait for unmount to complete, then navigate
setTimeout(() => {
(navigation as any).replace('PlayerAndroid', {
uri: stream.url,
@ -376,7 +389,7 @@ const AndroidVideoPlayer: React.FC = () => {
availableStreams: {},
groupedEpisodes: groupedEpisodes,
});
}, 100);
}, 300);
};
// Subtitle addon fetching
@ -489,73 +502,75 @@ const AndroidVideoPlayer: React.FC = () => {
/>
<View style={{ flex: 1, backgroundColor: 'black' }}>
<VideoSurface
processedStreamUrl={currentStreamUrl}
headers={headers}
volume={volume}
playbackSpeed={speedControl.playbackSpeed}
resizeMode={playerState.resizeMode}
paused={playerState.paused}
currentStreamUrl={currentStreamUrl}
toggleControls={toggleControls}
onLoad={handleLoad}
onProgress={handleProgress}
onSeek={(data) => {
playerState.isSeeking.current = false;
if (data.currentTime) traktAutosync.handleProgressUpdate(data.currentTime, playerState.duration, true);
}}
onEnd={() => {
if (modals.showEpisodeStreamsModal) return;
playerState.setPaused(true);
}}
onError={(err: any) => {
logger.error('Video Error', err);
{!isTransitioningStream && (
<VideoSurface
processedStreamUrl={currentStreamUrl}
headers={headers}
volume={volume}
playbackSpeed={speedControl.playbackSpeed}
resizeMode={playerState.resizeMode}
paused={playerState.paused}
currentStreamUrl={currentStreamUrl}
toggleControls={toggleControls}
onLoad={handleLoad}
onProgress={handleProgress}
onSeek={(data) => {
playerState.isSeeking.current = false;
if (data.currentTime) traktAutosync.handleProgressUpdate(data.currentTime, playerState.duration, true);
}}
onEnd={() => {
if (modals.showEpisodeStreamsModal) return;
playerState.setPaused(true);
}}
onError={(err: any) => {
logger.error('Video Error', err);
// Determine the actual error message
let displayError = 'An unknown error occurred';
// Determine the actual error message
let displayError = 'An unknown error occurred';
if (typeof err?.error === 'string') {
displayError = err.error;
} else if (err?.error?.errorString) {
displayError = err.error.errorString;
} else if (err?.errorString) {
displayError = err.errorString;
} else if (typeof err === 'string') {
displayError = err;
} else {
displayError = JSON.stringify(err);
}
if (typeof err?.error === 'string') {
displayError = err.error;
} else if (err?.error?.errorString) {
displayError = err.error.errorString;
} else if (err?.errorString) {
displayError = err.errorString;
} else if (typeof err === 'string') {
displayError = err;
} else {
displayError = JSON.stringify(err);
}
modals.setErrorDetails(displayError);
modals.setShowErrorModal(true);
}}
onBuffer={(buf) => playerState.setIsBuffering(buf.isBuffering)}
onTracksChanged={(data) => {
console.log('[AndroidVideoPlayer] onTracksChanged:', data);
if (data?.audioTracks) {
const formatted = data.audioTracks.map((t: any) => ({
id: t.id,
name: t.name || `Track ${t.id}`,
language: t.language
}));
tracksHook.setRnVideoAudioTracks(formatted);
}
if (data?.subtitleTracks) {
const formatted = data.subtitleTracks.map((t: any) => ({
id: t.id,
name: t.name || `Track ${t.id}`,
language: t.language
}));
tracksHook.setRnVideoTextTracks(formatted);
}
}}
mpvPlayerRef={mpvPlayerRef}
pinchRef={useRef(null)}
onPinchGestureEvent={() => { }}
onPinchHandlerStateChange={() => { }}
screenDimensions={playerState.screenDimensions}
useHardwareDecoding={settings.useHardwareDecoding}
/>
modals.setErrorDetails(displayError);
modals.setShowErrorModal(true);
}}
onBuffer={(buf) => playerState.setIsBuffering(buf.isBuffering)}
onTracksChanged={(data) => {
console.log('[AndroidVideoPlayer] onTracksChanged:', data);
if (data?.audioTracks) {
const formatted = data.audioTracks.map((t: any) => ({
id: t.id,
name: t.name || `Track ${t.id}`,
language: t.language
}));
tracksHook.setRnVideoAudioTracks(formatted);
}
if (data?.subtitleTracks) {
const formatted = data.subtitleTracks.map((t: any) => ({
id: t.id,
name: t.name || `Track ${t.id}`,
language: t.language
}));
tracksHook.setRnVideoTextTracks(formatted);
}
}}
mpvPlayerRef={mpvPlayerRef}
pinchRef={pinchRef}
onPinchGestureEvent={() => { }}
onPinchHandlerStateChange={() => { }}
screenDimensions={playerState.screenDimensions}
useHardwareDecoding={settings.useHardwareDecoding}
/>
)}
{/* Custom Subtitles for addon subtitles */}
<CustomSubtitles

View file

@ -8,6 +8,7 @@ import Animated, {
Easing,
SharedValue,
} from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { parentalGuideService } from '../../../services/parentalGuideService';
import { logger } from '../../../utils/logger';
import { useTheme } from '../../../contexts/ThemeContext';
@ -66,6 +67,7 @@ export const ParentalGuideOverlay: React.FC<ParentalGuideOverlayProps> = ({
shouldShow,
}) => {
const { currentTheme } = useTheme();
const insets = useSafeAreaInsets();
const screenWidth = Dimensions.get('window').width;
const [warnings, setWarnings] = useState<WarningItem[]>([]);
const [isVisible, setIsVisible] = useState(false);
@ -231,8 +233,12 @@ export const ParentalGuideOverlay: React.FC<ParentalGuideOverlayProps> = ({
const lineWidth = Math.min(3, screenWidth * 0.0038);
const containerPadding = Math.min(20, screenWidth * 0.025);
// Use left inset for landscape notches, top inset for portrait
const safeLeftOffset = insets.left + containerPadding;
const safeTopOffset = containerPadding;
return (
<Animated.View style={[styles.container, { left: containerPadding, top: containerPadding + 30 }]} pointerEvents="none">
<Animated.View style={[styles.container, { left: safeLeftOffset, top: safeTopOffset }]} pointerEvents="none">
{/* Vertical line - animates height */}
<Animated.View style={[styles.line, lineStyle, { backgroundColor: currentTheme.colors.primary, width: lineWidth }]} />