fix: bug
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
TouchableOpacity,
|
||||
Dimensions,
|
||||
ActivityIndicator,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { Video, ResizeMode, AVPlaybackStatus } from 'expo-av';
|
||||
import { Image } from 'expo-image';
|
||||
@@ -33,8 +34,10 @@ export interface VideoPlayerProps {
|
||||
maxHeight?: number;
|
||||
aspectRatio?: number;
|
||||
onReady?: (status: AVPlaybackStatus) => void;
|
||||
onWebLoadedData?: () => void;
|
||||
onError?: (error: any) => void;
|
||||
onPress?: () => void;
|
||||
fullscreenMode?: boolean; // 新增:全屏模式标志
|
||||
}
|
||||
|
||||
export function VideoPlayer({
|
||||
@@ -51,14 +54,18 @@ export function VideoPlayer({
|
||||
maxHeight,
|
||||
aspectRatio,
|
||||
onReady,
|
||||
onWebLoadedData,
|
||||
onError,
|
||||
onPress,
|
||||
fullscreenMode = false, // 新增:默认非全屏模式
|
||||
}: VideoPlayerProps) {
|
||||
const videoRef = useRef<Video>(null);
|
||||
const [videoStatus, setVideoStatus] = useState<AVPlaybackStatus>();
|
||||
const [videoMetadata, setVideoMetadata] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [showPosterOverlay, setShowPosterOverlay] = useState(showPoster);
|
||||
const [hasVideoError, setHasVideoError] = useState(false);
|
||||
const [shouldShowAsImage, setShouldShowAsImage] = useState(false);
|
||||
|
||||
const backgroundColor = useThemeColor({}, 'background');
|
||||
const placeholderColor = useThemeColor({}, 'imagePlaceholder');
|
||||
@@ -67,6 +74,16 @@ export function VideoPlayer({
|
||||
const isValidUrl = isValidVideoUrl(source.uri);
|
||||
const videoFileType = getVideoFileType(source.uri);
|
||||
|
||||
// 如果URL看起来不是视频,显示为图片
|
||||
useEffect(() => {
|
||||
if (!isValidUrl || !videoFileType) {
|
||||
console.log('URL不是有效的视频格式,将作为图片显示:', source.uri);
|
||||
setShouldShowAsImage(true);
|
||||
setHasVideoError(true);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [isValidUrl, videoFileType, source.uri]);
|
||||
|
||||
// 计算视频容器的最佳尺寸
|
||||
const calculateVideoDimensions = () => {
|
||||
const containerWidth = screenWidth - 40; // 减去padding
|
||||
@@ -132,7 +149,16 @@ export function VideoPlayer({
|
||||
// 处理视频加载错误
|
||||
const handleVideoError = (error: any) => {
|
||||
setIsLoading(false);
|
||||
setHasVideoError(true);
|
||||
console.error('视频加载失败:', error);
|
||||
|
||||
// 尝试作为图片显示
|
||||
const isImageUrl = source.uri.match(/\.(jpg|jpeg|png|gif|webp|bmp|svg)$/i);
|
||||
if (isImageUrl) {
|
||||
console.log('视频加载失败,尝试作为图片显示:', source.uri);
|
||||
setShouldShowAsImage(true);
|
||||
}
|
||||
|
||||
onError?.(error);
|
||||
};
|
||||
|
||||
@@ -153,6 +179,11 @@ export function VideoPlayer({
|
||||
return;
|
||||
}
|
||||
|
||||
// 全屏模式下不处理视频播放逻辑,只传递点击事件
|
||||
if (fullscreenMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果视频已加载但未播放,开始播放
|
||||
if (videoStatus?.isLoaded && !videoStatus.isPlaying && !useNativeControls) {
|
||||
videoRef.current?.playAsync();
|
||||
@@ -166,19 +197,50 @@ export function VideoPlayer({
|
||||
}
|
||||
}, [autoPlay, videoStatus]);
|
||||
|
||||
const isVideoLoaded = videoStatus?.isLoaded;
|
||||
const isVideoPlaying = videoStatus?.isLoaded && videoStatus.isPlaying;
|
||||
const isVideoLoaded = Platform.OS === 'web' ? !isLoading : videoStatus?.isLoaded;
|
||||
const isVideoPlaying = Platform.OS === 'web' ? true : (videoStatus?.isLoaded && videoStatus.isPlaying);
|
||||
|
||||
// 如果应该显示为图片,显示图片而不是视频
|
||||
if (shouldShowAsImage) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.container, { height: videoSize.height }, style]}
|
||||
onPress={handleContainerPress}
|
||||
activeOpacity={0.9}
|
||||
>
|
||||
<ThemedView style={[styles.videoContainer, { backgroundColor }]}>
|
||||
<Image
|
||||
source={{ uri: source.uri }}
|
||||
style={[
|
||||
styles.poster,
|
||||
{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: placeholderColor,
|
||||
},
|
||||
]}
|
||||
contentFit="cover"
|
||||
onLoad={() => setIsLoading(false)}
|
||||
onError={(error) => {
|
||||
console.error('图片加载失败:', error);
|
||||
setIsLoading(false);
|
||||
}}
|
||||
/>
|
||||
</ThemedView>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.container, { height: videoSize.height }, style]}
|
||||
onPress={handleContainerPress}
|
||||
activeOpacity={0.9}
|
||||
disabled={!onPress && useNativeControls}
|
||||
activeOpacity={fullscreenMode ? 0.8 : 0.9}
|
||||
disabled={fullscreenMode ? false : (!onPress && useNativeControls)}
|
||||
>
|
||||
<ThemedView style={[styles.videoContainer, { backgroundColor }]}>
|
||||
{/* 封面图 */}
|
||||
{poster && showPosterOverlay && (
|
||||
{/* 封面图 - 只在非 Web 平台显示 */}
|
||||
{poster && showPosterOverlay && Platform.OS !== 'web' && !hasVideoError && (
|
||||
<Image
|
||||
source={{ uri: poster }}
|
||||
style={[
|
||||
@@ -194,35 +256,87 @@ export function VideoPlayer({
|
||||
)}
|
||||
|
||||
{/* 视频播放器 */}
|
||||
<Video
|
||||
ref={videoRef}
|
||||
source={source}
|
||||
style={[
|
||||
styles.video,
|
||||
{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
]}
|
||||
resizeMode={resizeMode}
|
||||
shouldPlay={shouldPlay}
|
||||
isLooping={isLooping}
|
||||
isMuted={isMuted}
|
||||
useNativeControls={useNativeControls}
|
||||
onReadyForDisplay={handleVideoReady}
|
||||
onError={handleVideoError}
|
||||
onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
|
||||
/>
|
||||
{!hasVideoError && (
|
||||
<>
|
||||
{Platform.OS === 'web' ? (
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={source.uri}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
overflow: 'hidden',
|
||||
cursor: fullscreenMode ? 'pointer' : 'default',
|
||||
}}
|
||||
autoPlay={autoPlay}
|
||||
loop={isLooping}
|
||||
muted={isMuted}
|
||||
playsInline
|
||||
onClick={(e) => {
|
||||
// 全屏模式下,点击视频直接触发关闭
|
||||
if (fullscreenMode && onPress) {
|
||||
e.stopPropagation();
|
||||
onPress();
|
||||
}
|
||||
}}
|
||||
onLoadedData={() => {
|
||||
setIsLoading(false);
|
||||
onWebLoadedData?.(); // 通知父组件Web平台视频已加载
|
||||
}}
|
||||
onError={handleVideoError}
|
||||
/>
|
||||
) : (
|
||||
<Video
|
||||
ref={videoRef}
|
||||
source={source}
|
||||
style={styles.video}
|
||||
resizeMode={resizeMode}
|
||||
shouldPlay={shouldPlay}
|
||||
isLooping={isLooping}
|
||||
isMuted={isMuted}
|
||||
useNativeControls={useNativeControls}
|
||||
onReadyForDisplay={handleVideoReady}
|
||||
onError={handleVideoError}
|
||||
onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 加载指示器 */}
|
||||
{isLoading && (
|
||||
{/* 错误状态下显示为图片 */}
|
||||
{hasVideoError && !shouldShowAsImage && (
|
||||
<Image
|
||||
source={{ uri: source.uri }}
|
||||
style={[
|
||||
styles.poster,
|
||||
{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: placeholderColor,
|
||||
},
|
||||
]}
|
||||
contentFit="cover"
|
||||
onLoad={() => setIsLoading(false)}
|
||||
onError={(error) => {
|
||||
console.error('错误回退:图片也加载失败:', error);
|
||||
setIsLoading(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 加载指示器 - 只在非 Web 平台显示 */}
|
||||
{isLoading && Platform.OS !== 'web' && !hasVideoError && (
|
||||
<View style={styles.loadingOverlay}>
|
||||
<ActivityIndicator size="large" color="#4ECDC4" />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 播放按钮覆盖层(当视频暂停且无原生控件时显示) */}
|
||||
{!useNativeControls && isVideoLoaded && !isVideoPlaying && !isLoading && (
|
||||
{/* 播放按钮覆盖层(当视频暂停且无原生控件时显示) - 全屏模式下不显示 */}
|
||||
{!useNativeControls && isVideoLoaded && !isVideoPlaying && !isLoading && !hasVideoError && !fullscreenMode && (
|
||||
<View style={styles.playButtonOverlay}>
|
||||
<View style={styles.playButton}>
|
||||
<ThemedView style={styles.playIcon}>
|
||||
@@ -232,8 +346,8 @@ export function VideoPlayer({
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 错误状态 */}
|
||||
{!isLoading && !isVideoLoaded && (
|
||||
{/* 错误状态 - 只在非 Web 平台显示 */}
|
||||
{!isLoading && !isVideoLoaded && hasVideoError && Platform.OS !== 'web' && !shouldShowAsImage && (
|
||||
<View style={styles.errorOverlay}>
|
||||
<ThemedView style={styles.errorMessage}>
|
||||
{'❌'}
|
||||
@@ -265,8 +379,10 @@ const styles = StyleSheet.create({
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
poster: {
|
||||
poster: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
|
||||
Reference in New Issue
Block a user