feat(player): enhance video playback with SSL error fallback and performance optimizations

- Add comprehensive SSL certificate error detection and automatic source switching
- Implement smart video source fallback strategy with failed source tracking
- Enhance video component with optimized event handlers and useCallback patterns
- Add explicit playAsync() call in onLoad to improve auto-play reliability
- Integrate performance monitoring with detailed logging throughout playback chain
- Optimize Video component props with useMemo and custom useVideoHandlers hook
- Add source matching fixes for fallback scenarios in DetailStore
- Enhance error handling with user-friendly messages and recovery strategies
This commit is contained in:
zimplexing
2025-08-15 22:41:18 +08:00
parent 172815f926
commit 836285dbd5
7 changed files with 761 additions and 116 deletions

131
hooks/useVideoHandlers.ts Normal file
View File

@@ -0,0 +1,131 @@
import { useCallback, RefObject, useMemo } from 'react';
import { Video, ResizeMode } from 'expo-av';
import Toast from 'react-native-toast-message';
import usePlayerStore from '@/stores/playerStore';
interface UseVideoHandlersProps {
videoRef: RefObject<Video>;
currentEpisode: { url: string; title: string } | undefined;
initialPosition: number;
introEndTime?: number;
playbackRate: number;
handlePlaybackStatusUpdate: (status: any) => void;
deviceType: string;
detail?: { poster?: string };
}
export const useVideoHandlers = ({
videoRef,
currentEpisode,
initialPosition,
introEndTime,
playbackRate,
handlePlaybackStatusUpdate,
deviceType,
detail,
}: UseVideoHandlersProps) => {
const onLoad = useCallback(async () => {
console.info(`[PERF] Video onLoad - video ready to play`);
try {
// 1. 先设置位置(如果需要)
const jumpPosition = initialPosition || introEndTime || 0;
if (jumpPosition > 0) {
console.info(`[PERF] Setting initial position to ${jumpPosition}ms`);
await videoRef.current?.setPositionAsync(jumpPosition);
}
// 2. 显式调用播放以确保自动播放
console.info(`[AUTOPLAY] Attempting to start playback after onLoad`);
await videoRef.current?.playAsync();
console.info(`[AUTOPLAY] Auto-play successful after onLoad`);
usePlayerStore.setState({ isLoading: false });
console.info(`[PERF] Video loading complete - isLoading set to false`);
} catch (error) {
console.warn(`[AUTOPLAY] Failed to auto-play after onLoad:`, error);
// 即使自动播放失败,也要设置加载完成状态
usePlayerStore.setState({ isLoading: false });
// 不显示错误提示,因为自动播放失败是常见且预期的情况
}
}, [videoRef, initialPosition, introEndTime]);
const onLoadStart = useCallback(() => {
if (!currentEpisode?.url) return;
console.info(`[PERF] Video onLoadStart - starting to load video: ${currentEpisode.url.substring(0, 100)}...`);
usePlayerStore.setState({ isLoading: true });
}, [currentEpisode?.url]);
const onError = useCallback((error: any) => {
if (!currentEpisode?.url) return;
console.error(`[ERROR] Video playback error:`, error);
// 检测SSL证书错误和其他网络错误
const errorString = (error as any)?.error?.toString() || error?.toString() || '';
const isSSLError = errorString.includes('SSLHandshakeException') ||
errorString.includes('CertPathValidatorException') ||
errorString.includes('Trust anchor for certification path not found');
const isNetworkError = errorString.includes('HttpDataSourceException') ||
errorString.includes('IOException') ||
errorString.includes('SocketTimeoutException');
if (isSSLError) {
console.error(`[SSL_ERROR] SSL certificate validation failed for URL: ${currentEpisode.url}`);
Toast.show({
type: "error",
text1: "SSL证书错误正在尝试其他播放源...",
text2: "请稍候"
});
usePlayerStore.getState().handleVideoError('ssl', currentEpisode.url);
} else if (isNetworkError) {
console.error(`[NETWORK_ERROR] Network connection failed for URL: ${currentEpisode.url}`);
Toast.show({
type: "error",
text1: "网络连接失败,正在尝试其他播放源...",
text2: "请稍候"
});
usePlayerStore.getState().handleVideoError('network', currentEpisode.url);
} else {
console.error(`[VIDEO_ERROR] Other video error for URL: ${currentEpisode.url}`);
Toast.show({
type: "error",
text1: "视频播放失败,正在尝试其他播放源...",
text2: "请稍候"
});
usePlayerStore.getState().handleVideoError('other', currentEpisode.url);
}
}, [currentEpisode?.url]);
// 优化的Video组件props
const videoProps = useMemo(() => ({
source: { uri: currentEpisode?.url || '' },
posterSource: { uri: detail?.poster ?? "" },
resizeMode: ResizeMode.CONTAIN,
rate: playbackRate,
onPlaybackStatusUpdate: handlePlaybackStatusUpdate,
onLoad,
onLoadStart,
onError,
useNativeControls: deviceType !== 'tv',
shouldPlay: true,
}), [
currentEpisode?.url,
detail?.poster,
playbackRate,
handlePlaybackStatusUpdate,
onLoad,
onLoadStart,
onError,
deviceType,
]);
return {
onLoad,
onLoadStart,
onError,
videoProps,
};
};