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

优化Expo AV实现FlatList中高效视频缓存方案咨询

解决方案

核心思路

通过分阶段加载视频内容实现需求:未播放时仅加载并缓存前2秒片段,用户触发播放后再加载完整视频并缓存剩余内容。结合Expo AV的加载参数配置与组件状态管理,配合FlatList的内存优化机制来实现。


1. 视频项组件的分阶段加载实现

每个视频项维护播放状态与加载状态,初始加载2秒预览片段,播放时切换为完整视频加载:

import { Video } from 'expo-av';
import { useState, useEffect, useRef } from 'react';
import { TouchableOpacity, Text, View } from 'react-native';

const VideoListItem = ({ videoUri }) => {
  const [isPlaying, setIsPlaying] = useState(false);
  const [isFullLoaded, setIsFullLoaded] = useState(false);
  const videoRef = useRef(null);

  // 加载2秒预览片段
  const loadPreviewSegment = async () => {
    if (!videoRef.current) return;
    try {
      await videoRef.current.loadAsync(
        { uri: videoUri },
        {
          startTime: 0,
          endTime: 2,
          shouldPlay: false,
          cachePolicy: 'cache-only', // 仅缓存加载的2秒片段
        }
      );
    } catch (err) {
      console.error('预览片段加载失败:', err);
    }
  };

  // 加载完整视频
  const loadFullVideo = async () => {
    if (!videoRef.current) return;
    try {
      await videoRef.current.loadAsync(
        { uri: videoUri },
        {
          shouldPlay: true,
          cachePolicy: 'cache-then-network', // 优先用缓存,再请求剩余内容
        }
      );
      setIsFullLoaded(true);
    } catch (err) {
      console.error('完整视频加载失败:', err);
    }
  };

  // 处理播放/暂停逻辑
  const togglePlayback = async () => {
    if (!videoRef.current) return;
    if (!isFullLoaded) {
      loadFullVideo();
      setIsPlaying(true);
    } else {
      if (isPlaying) {
        await videoRef.current.pauseAsync();
      } else {
        await videoRef.current.playAsync();
      }
      setIsPlaying(!isPlaying);
    }
  };

  // 组件挂载时加载预览,卸载时释放资源
  useEffect(() => {
    loadPreviewSegment();
    return () => {
      if (videoRef.current) {
        videoRef.current.unloadAsync(); // 卸载视频,释放内存缓存
      }
    };
  }, [videoUri]);

  return (
    <View style={{ marginBottom: 10 }}>
      <Video
        ref={videoRef}
        style={{ width: '100%', height: 250 }}
        resizeMode="cover"
        onPlaybackStatusUpdate={(status) => {
          if (status.didJustFinish) {
            setIsPlaying(false);
          }
        }}
      />
      <TouchableOpacity
        onPress={togglePlayback}
        style={{ padding: 10, backgroundColor: '#eee' }}
      >
        <Text>{isPlaying ? '暂停' : '播放'}</Text>
      </TouchableOpacity>
    </View>
  );
};

export default VideoListItem;

2. FlatList的内存优化配置

针对无限滚动场景,优化FlatList的渲染与资源释放,避免内存堆积:

import { FlatList } from 'react-native';
import VideoListItem from './VideoListItem';

const VideoScrollList = ({ videoList }) => {
  return (
    <FlatList
      data={videoList}
      renderItem={({ item }) => <VideoListItem videoUri={item.uri} />}
      keyExtractor={(item) => item.id}
      removeClippedSubviews={true} // 自动卸载不可见区域的组件
      windowSize={5} // 仅渲染当前可见区域前后5个视频项
      initialNumToRender={3} // 初始渲染3个视频项
    />
  );
};

export default VideoScrollList;

3. 关键注意事项

  • 缓存策略验证:确保你的Expo SDK版本支持startTime/endTime与cachePolicy参数的组合使用,建议使用最新稳定版Expo SDK。
  • 资源释放:必须在组件卸载时调用unloadAsync(),否则Expo AV会持续占用内存缓存已加载的视频内容。
  • 网络兼容性:如果视频服务器不支持HTTP范围请求,startTime/endTime参数可能无法生效,此时可以考虑预先生成2秒预览片段的独立URL。
  • 状态复用:对于已播放过的视频,可以在全局状态中标记为"已完整加载",下次渲染时直接加载完整视频,提升用户体验。

内容的提问来源于stack exchange,提问作者omar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 09:05:34