You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 19:34:53