Files
OrionTV/hooks/useVideoHandlers.ts
zimplexing 836285dbd5 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
2025-08-15 22:41:18 +08:00

131 lines
4.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
};
};