mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 00:32:04 +00:00
vlc fix
This commit is contained in:
parent
37ece44b9b
commit
5764825c1d
1 changed files with 128 additions and 37 deletions
|
|
@ -87,6 +87,16 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
const TEMP_FORCE_VLC = false;
|
const TEMP_FORCE_VLC = false;
|
||||||
const useVLC = Platform.OS === 'android' && (TEMP_FORCE_VLC || forceVlc);
|
const useVLC = Platform.OS === 'android' && (TEMP_FORCE_VLC || forceVlc);
|
||||||
|
|
||||||
|
// Log player selection
|
||||||
|
useEffect(() => {
|
||||||
|
const playerType = useVLC ? 'VLC (expo-libvlc-player)' : 'React Native Video';
|
||||||
|
const reason = useVLC
|
||||||
|
? (TEMP_FORCE_VLC ? 'TEMP_FORCE_VLC=true' : `forceVlc=${forceVlc} from route params`)
|
||||||
|
: 'default react-native-video';
|
||||||
|
console.log(`🎬 [AndroidVideoPlayer] Using ${playerType} - ${reason}`);
|
||||||
|
logger.log(`[AndroidVideoPlayer] Player selection: ${playerType} (${reason})`);
|
||||||
|
}, [useVLC, forceVlc]);
|
||||||
|
|
||||||
|
|
||||||
// Check if the stream is HLS (m3u8 playlist)
|
// Check if the stream is HLS (m3u8 playlist)
|
||||||
const isHlsStream = (url: string) => {
|
const isHlsStream = (url: string) => {
|
||||||
|
|
@ -265,17 +275,41 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
const vlcRef = useRef<any>(null);
|
const vlcRef = useRef<any>(null);
|
||||||
const [vlcActive, setVlcActive] = useState(true);
|
const [vlcActive, setVlcActive] = useState(true);
|
||||||
|
|
||||||
// Compute VLC aspect ratio mapping from current resize mode
|
// Compute aspect ratio string for VLC (e.g., "16:9") based on current screen and resizeMode
|
||||||
|
const toVlcRatio = useCallback((w: number, h: number): string => {
|
||||||
|
const a = Math.max(1, Math.round(w));
|
||||||
|
const b = Math.max(1, Math.round(h));
|
||||||
|
const gcd = (x: number, y: number): number => (y === 0 ? x : gcd(y, x % y));
|
||||||
|
const g = gcd(a, b);
|
||||||
|
return `${Math.floor(a / g)}:${Math.floor(b / g)}`;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const vlcAspectRatio = useMemo(() => {
|
const vlcAspectRatio = useMemo(() => {
|
||||||
if (!useVLC) return undefined;
|
if (!useVLC) return undefined as string | undefined;
|
||||||
// Contain/original behavior -> let VLC choose best fit
|
// For VLC, we handle aspect ratio through custom zoom for cover mode
|
||||||
if (resizeMode === 'contain' || resizeMode === 'none') return undefined;
|
// Only force aspect for fill mode (stretch to fit)
|
||||||
// For cover/fill, force the view's aspect ratio to fill the container
|
if (resizeMode === 'fill') {
|
||||||
if ((resizeMode === 'cover' || resizeMode === 'fill') && screenDimensions.width > 0 && screenDimensions.height > 0) {
|
const sw = screenDimensions.width || 0;
|
||||||
return `${Math.round(screenDimensions.width)}:${Math.round(screenDimensions.height)}`;
|
const sh = screenDimensions.height || 0;
|
||||||
|
if (sw > 0 && sh > 0) {
|
||||||
|
return toVlcRatio(sw, sh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// For cover/contain/none: let VLC preserve natural aspect, we handle zoom separately
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [useVLC, resizeMode, screenDimensions.width, screenDimensions.height]);
|
}, [useVLC, resizeMode, screenDimensions.width, screenDimensions.height, toVlcRatio]);
|
||||||
|
|
||||||
|
// VLC options for better playback
|
||||||
|
const vlcOptions = useMemo(() => {
|
||||||
|
if (!useVLC) return [] as string[];
|
||||||
|
// Basic options for network streaming
|
||||||
|
return [
|
||||||
|
'--network-caching=2000',
|
||||||
|
'--clock-jitter=0',
|
||||||
|
'--http-reconnect',
|
||||||
|
'--sout-mux-caching=2000'
|
||||||
|
];
|
||||||
|
}, [useVLC]);
|
||||||
|
|
||||||
|
|
||||||
// Volume and brightness controls
|
// Volume and brightness controls
|
||||||
|
|
@ -581,11 +615,22 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
screenDimensions.height
|
screenDimensions.height
|
||||||
);
|
);
|
||||||
setCustomVideoStyles(styles);
|
setCustomVideoStyles(styles);
|
||||||
|
|
||||||
|
// Recalculate zoom for cover mode when video aspect ratio changes
|
||||||
|
if (resizeMode === 'cover') {
|
||||||
|
const screenAspect = screenDimensions.width / screenDimensions.height;
|
||||||
|
const zoomFactor = Math.max(screenAspect / videoAspectRatio, videoAspectRatio / screenAspect);
|
||||||
|
setZoomScale(zoomFactor);
|
||||||
|
if (DEBUG_MODE) {
|
||||||
|
logger.log(`[AndroidVideoPlayer] Cover zoom updated: ${zoomFactor.toFixed(2)}x (video AR: ${videoAspectRatio.toFixed(2)})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
if (__DEV__) logger.log(`[AndroidVideoPlayer] Screen dimensions changed, recalculated styles:`, styles);
|
if (__DEV__) logger.log(`[AndroidVideoPlayer] Screen dimensions changed, recalculated styles:`, styles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [screenDimensions, videoAspectRatio]);
|
}, [screenDimensions, videoAspectRatio, resizeMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = Dimensions.addEventListener('change', ({ screen }) => {
|
const subscription = Dimensions.addEventListener('change', ({ screen }) => {
|
||||||
|
|
@ -1245,15 +1290,47 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const cycleAspectRatio = () => {
|
const cycleAspectRatio = () => {
|
||||||
// Cycle through allowed resize modes per platform
|
// Cycle through allowed resize modes per platform, but exclude 'contain' for VLC
|
||||||
const resizeModes: ResizeModeType[] = Platform.OS === 'ios'
|
let resizeModes: ResizeModeType[];
|
||||||
? ['cover', 'fill']
|
if (Platform.OS === 'ios') {
|
||||||
: ['contain', 'cover', 'fill', 'none'];
|
resizeModes = ['cover', 'fill'];
|
||||||
|
} else if (useVLC) {
|
||||||
|
// VLC doesn't handle contain well, so exclude it
|
||||||
|
resizeModes = ['cover', 'fill', 'none'];
|
||||||
|
} else {
|
||||||
|
resizeModes = ['contain', 'cover', 'fill', 'none'];
|
||||||
|
}
|
||||||
|
|
||||||
const currentIndex = resizeModes.indexOf(resizeMode);
|
const currentIndex = resizeModes.indexOf(resizeMode);
|
||||||
const nextIndex = (currentIndex + 1) % resizeModes.length;
|
const nextIndex = (currentIndex + 1) % resizeModes.length;
|
||||||
setResizeMode(resizeModes[nextIndex]);
|
const newResizeMode = resizeModes[nextIndex];
|
||||||
|
setResizeMode(newResizeMode);
|
||||||
|
|
||||||
|
// Set zoom for cover mode to crop/fill screen
|
||||||
|
if (newResizeMode === 'cover') {
|
||||||
|
if (videoAspectRatio && screenDimensions.width && screenDimensions.height) {
|
||||||
|
const screenAspect = screenDimensions.width / screenDimensions.height;
|
||||||
|
const videoAspect = videoAspectRatio;
|
||||||
|
// Calculate zoom needed to fill screen (cover mode crops to fill)
|
||||||
|
const zoomFactor = Math.max(screenAspect / videoAspect, videoAspect / screenAspect);
|
||||||
|
setZoomScale(zoomFactor);
|
||||||
|
if (DEBUG_MODE) {
|
||||||
|
logger.log(`[AndroidVideoPlayer] Cover mode zoom: ${zoomFactor.toFixed(2)}x (screen: ${screenAspect.toFixed(2)}, video: ${videoAspect.toFixed(2)})`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback if video aspect not available yet - will be set when video loads
|
||||||
|
setZoomScale(1.2); // Conservative zoom that works for most content
|
||||||
|
if (DEBUG_MODE) {
|
||||||
|
logger.log(`[AndroidVideoPlayer] Cover mode zoom fallback: 1.2x (video AR not available yet)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (newResizeMode === 'contain' || newResizeMode === 'none') {
|
||||||
|
// Reset zoom for other modes
|
||||||
|
setZoomScale(1);
|
||||||
|
}
|
||||||
|
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
logger.log(`[AndroidVideoPlayer] Resize mode changed to: ${resizeModes[nextIndex]}`);
|
logger.log(`[AndroidVideoPlayer] Resize mode changed to: ${newResizeMode}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2707,15 +2784,16 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
delayLongPress={300}
|
delayLongPress={300}
|
||||||
>
|
>
|
||||||
{useVLC ? (
|
{useVLC ? (
|
||||||
<LibVlcPlayerView
|
<>
|
||||||
|
{console.log('🎬 [AndroidVideoPlayer] Rendering VLC player component')}
|
||||||
|
<LibVlcPlayerView
|
||||||
ref={vlcRef}
|
ref={vlcRef}
|
||||||
style={[styles.video, customVideoStyles, { transform: [{ scale: zoomScale }] }]}
|
style={[styles.video, customVideoStyles, { transform: [{ scale: zoomScale }] }]}
|
||||||
// Remount control
|
// Remount control
|
||||||
key={vlcActive ? 'vlc-on' : 'vlc-off'}
|
key={vlcActive ? 'vlc-on' : 'vlc-off'}
|
||||||
source={currentStreamUrl}
|
source={currentStreamUrl}
|
||||||
aspectRatio={vlcAspectRatio}
|
aspectRatio={vlcAspectRatio}
|
||||||
// When using contain/original, use scale 0 to let VLC manage
|
options={vlcOptions}
|
||||||
scale={resizeMode === 'contain' || resizeMode === 'none' ? 0 : 0}
|
|
||||||
volume={Math.round(Math.max(0, Math.min(1, volume)) * 100)}
|
volume={Math.round(Math.max(0, Math.min(1, volume)) * 100)}
|
||||||
mute={false}
|
mute={false}
|
||||||
repeat={false}
|
repeat={false}
|
||||||
|
|
@ -2725,6 +2803,8 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
time={Math.max(0, Math.floor(currentTime * 1000))}
|
time={Math.max(0, Math.floor(currentTime * 1000))}
|
||||||
onFirstPlay={(info: any) => {
|
onFirstPlay={(info: any) => {
|
||||||
try {
|
try {
|
||||||
|
console.log('🎬 [VLC] Video loaded successfully');
|
||||||
|
logger.log('[AndroidVideoPlayer][VLC] Video loaded successfully');
|
||||||
const lenSec = (info?.length ?? 0) / 1000;
|
const lenSec = (info?.length ?? 0) / 1000;
|
||||||
const width = info?.width || 0;
|
const width = info?.width || 0;
|
||||||
const height = info?.height || 0;
|
const height = info?.height || 0;
|
||||||
|
|
@ -2743,11 +2823,18 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
onPlaying={() => setPaused(false)}
|
onPlaying={() => setPaused(false)}
|
||||||
onPaused={() => setPaused(true)}
|
onPaused={() => setPaused(true)}
|
||||||
onEndReached={onEnd}
|
onEndReached={onEnd}
|
||||||
onEncounteredError={(e: any) => handleError(e)}
|
onEncounteredError={(e: any) => {
|
||||||
|
console.log('🎬 [VLC] Encountered error:', e);
|
||||||
|
logger.error('[AndroidVideoPlayer][VLC] Encountered error:', e);
|
||||||
|
handleError(e);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Video
|
<>
|
||||||
ref={videoRef}
|
{console.log('🎬 [AndroidVideoPlayer] Rendering React Native Video component')}
|
||||||
|
<Video
|
||||||
|
ref={videoRef}
|
||||||
style={[styles.video, customVideoStyles, { transform: [{ scale: zoomScale }] }]}
|
style={[styles.video, customVideoStyles, { transform: [{ scale: zoomScale }] }]}
|
||||||
source={{
|
source={{
|
||||||
uri: currentStreamUrl,
|
uri: currentStreamUrl,
|
||||||
|
|
@ -2755,23 +2842,25 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
type: isHlsStream(currentStreamUrl) ? 'm3u8' : (currentVideoType as any)
|
type: isHlsStream(currentStreamUrl) ? 'm3u8' : (currentVideoType as any)
|
||||||
}}
|
}}
|
||||||
paused={paused}
|
paused={paused}
|
||||||
onLoadStart={() => {
|
onLoadStart={() => {
|
||||||
loadStartAtRef.current = Date.now();
|
console.log('🎬 [RN Video] Load started');
|
||||||
logger.log('[AndroidVideoPlayer] onLoadStart');
|
loadStartAtRef.current = Date.now();
|
||||||
|
logger.log('[AndroidVideoPlayer][RN Video] onLoadStart');
|
||||||
// Log stream information for debugging
|
|
||||||
const streamInfo = {
|
// Log stream information for debugging
|
||||||
url: currentStreamUrl,
|
const streamInfo = {
|
||||||
isHls: isHlsStream(currentStreamUrl),
|
url: currentStreamUrl,
|
||||||
videoType: currentVideoType,
|
isHls: isHlsStream(currentStreamUrl),
|
||||||
headers: headers || getStreamHeaders(),
|
videoType: currentVideoType,
|
||||||
provider: currentStreamProvider || streamProvider
|
headers: headers || getStreamHeaders(),
|
||||||
};
|
provider: currentStreamProvider || streamProvider
|
||||||
logger.log('[AndroidVideoPlayer] Stream info:', streamInfo);
|
};
|
||||||
}}
|
logger.log('[AndroidVideoPlayer][RN Video] Stream info:', streamInfo);
|
||||||
|
}}
|
||||||
onProgress={handleProgress}
|
onProgress={handleProgress}
|
||||||
onLoad={(e) => {
|
onLoad={(e) => {
|
||||||
logger.log('[AndroidVideoPlayer] onLoad fired', { duration: e?.duration });
|
console.log('🎬 [RN Video] Video loaded successfully');
|
||||||
|
logger.log('[AndroidVideoPlayer][RN Video] onLoad fired', { duration: e?.duration });
|
||||||
onLoad(e);
|
onLoad(e);
|
||||||
}}
|
}}
|
||||||
onReadyForDisplay={() => {
|
onReadyForDisplay={() => {
|
||||||
|
|
@ -2787,7 +2876,8 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
onSeek={onSeek}
|
onSeek={onSeek}
|
||||||
onEnd={onEnd}
|
onEnd={onEnd}
|
||||||
onError={(err) => {
|
onError={(err) => {
|
||||||
logger.error('[AndroidVideoPlayer] onError', err);
|
console.log('🎬 [RN Video] Encountered error:', err);
|
||||||
|
logger.error('[AndroidVideoPlayer][RN Video] onError', err);
|
||||||
handleError(err);
|
handleError(err);
|
||||||
}}
|
}}
|
||||||
onBuffer={(buf) => {
|
onBuffer={(buf) => {
|
||||||
|
|
@ -2816,6 +2906,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
// Use textureView on Android: allows 3D mapping but DRM not supported
|
// Use textureView on Android: allows 3D mapping but DRM not supported
|
||||||
viewType={Platform.OS === 'android' ? ViewType.TEXTURE : undefined}
|
viewType={Platform.OS === 'android' ? ViewType.TEXTURE : undefined}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue