如何基于expo-video而非expo-av获取视频naturalSize?
在expo-video中获取视频naturalSize实现Instagram式视频播放
expo-video文档确实没有明确标注获取视频原始尺寸(naturalSize)的方法,但可以通过两种可靠方式实现,进而完成像素完美的信息流列表和点击展示原始尺寸的功能:
一、获取视频naturalSize的两种方法
1. 利用expo-video的onLoad事件回调
虽然文档未明确说明,但Video组件的onLoad事件会返回包含原始尺寸的原生事件对象,直接提取即可:
import { Video } from 'expo-video'; <Video source={{ uri: '你的视频URI' }} onLoad={(event) => { const { width, height } = event.nativeEvent.naturalSize; // 保存width和height作为视频的naturalSize }} resizeMode="cover" shouldPlay={false} />
2. 通过expo-media-library预加载元数据
如果需要在视频渲染前就获取原始尺寸,可以用expo-media-library读取视频资产信息:
import { MediaLibrary } from 'expo'; // 先请求媒体权限(需在app.json中配置权限) const getVideoNaturalSize = async (videoUri) => { const { status } = await MediaLibrary.requestPermissionsAsync(); if (status !== 'granted') return null; const assets = await MediaLibrary.getAssetsAsync({ mediaType: 'video', filter: MediaLibrary.filterByUri(videoUri), limit: 1, }); if (assets.totalCount === 0) return null; const asset = assets.assets[0]; return { width: asset.width, height: asset.height }; };
二、实现Instagram式视频信息流与全屏展示
1. 像素完美的信息流列表
根据获取到的naturalSize计算视频容器高度,保持原始比例,确保信息流展示无拉伸:
import { useState, useEffect } from 'react'; import { View, TouchableOpacity, StyleSheet, Dimensions } from 'react-native'; import { Video } from 'expo-video'; const { width: screenWidth } = Dimensions.get('window'); const VideoFeedItem = ({ videoUri, onPress }) => { const [naturalSize, setNaturalSize] = useState(null); useEffect(() => { const fetchSize = async () => { const size = await getVideoNaturalSize(videoUri); setNaturalSize(size); }; fetchSize(); }, [videoUri]); // 计算容器高度,基于屏幕宽度和视频比例 const containerHeight = naturalSize ? (naturalSize.height / naturalSize.width) * screenWidth : 200; // 加载前的默认高度 return ( <TouchableOpacity onPress={() => onPress(naturalSize)}> <View style={[styles.videoContainer, { height: containerHeight }]}> <Video source={{ uri: videoUri }} style={styles.video} resizeMode="cover" shouldPlay={false} onLoad={(event) => { if (!naturalSize) setNaturalSize(event.nativeEvent.naturalSize); }} /> </View> </TouchableOpacity> ); }; const styles = StyleSheet.create({ videoContainer: { width: screenWidth, marginVertical: 8, }, video: { flex: 1, width: '100%', height: '100%', }, });
2. 点击全屏展示原始尺寸
根据naturalSize适配全屏视频的显示,确保按原始比例展示,无变形:
import { Modal, View, StyleSheet, Dimensions } from 'react-native'; import { Video } from 'expo-video'; const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); const FullScreenVideo = ({ visible, videoUri, naturalSize, onClose }) => { let videoStyle = {}; if (naturalSize) { const videoRatio = naturalSize.width / naturalSize.height; const screenRatio = screenWidth / screenHeight; // 根据比例适配,优先保证视频完整显示 if (videoRatio > screenRatio) { videoStyle.width = screenWidth; videoStyle.height = screenWidth / videoRatio; } else { videoStyle.height = screenHeight; videoStyle.width = screenHeight * videoRatio; } } return ( <Modal visible={visible} animationType="fade" transparent={false} onRequestClose={onClose}> <View style={styles.modalContainer}> {/* 关闭按钮,可替换为自定义图标 */} <TouchableOpacity style={styles.closeBtn} onPress={onClose} /> <Video source={{ uri: videoUri }} style={[styles.fullVideo, videoStyle]} resizeMode="contain" shouldPlay={true} isLooping={true} /> </View> </Modal> ); }; const styles = StyleSheet.create({ modalContainer: { flex: 1, backgroundColor: '#000', justifyContent: 'center', alignItems: 'center', }, closeBtn: { position: 'absolute', top: 40, right: 20, width: 40, height: 40, }, fullVideo: { alignSelf: 'center', }, });
3. 整合主组件
将信息流和全屏组件结合,实现完整功能:
import { useState } from 'react'; import { View, StyleSheet } from 'react-native'; const VideoFeed = () => { const [selectedVideo, setSelectedVideo] = useState(null); const [selectedSize, setSelectedSize] = useState(null); // 替换为你的视频列表 const videoList = [{ uri: '视频1URI' }, { uri: '视频2URI' }]; const handleVideoTap = (size, uri) => { setSelectedVideo(uri); setSelectedSize(size); }; return ( <View style={styles.container}> {videoList.map((video, idx) => ( <VideoFeedItem key={idx} videoUri={video.uri} onPress={(size) => handleVideoTap(size, video.uri)} /> ))} <FullScreenVideo visible={!!selectedVideo} videoUri={selectedVideo} naturalSize={selectedSize} onClose={() => setSelectedVideo(null)} /> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, }, });
内容的提问来源于stack exchange,提问作者wajid khan
相关产品推荐
相关产品推荐

