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

Flutter实现类Reels可滑动视频流:预加载与无缝播放方案咨询

Flutter Reels式视频流实现(带预加载)

核心实现思路

  • 用PageView实现垂直滑动的视频列表,每个页面对应一个视频
  • 维护VideoPlayerController数组,统一管理所有视频的加载/播放状态
  • 监听当前视频的播放进度,当剩余时长≤10秒时,初始化并预加载下一个视频的控制器
  • 页面切换时自动暂停上一个视频、播放当前视频,同时按需清理超出范围的控制器以节省资源

完整代码示例

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

class ReelsVideoStream extends StatefulWidget {
  final List<String> videoUrls; // 传入20个视频的URL数组

  const ReelsVideoStream({super.key, required this.videoUrls});

  @override
  State<ReelsVideoStream> createState() => _ReelsVideoStreamState();
}

class _ReelsVideoStreamState extends State<ReelsVideoStream> {
  late PageController _pageController;
  List<VideoPlayerController?> _controllers = [];
  int _currentPage = 0;
  bool _isLoadingNext = false;

  @override
  void initState() {
    super.initState();
    _pageController = PageController(initialPage: 0);
    _initControllers();
    _pageController.addListener(_onPageChanged);
  }

  // 初始化当前及下一个视频的控制器
  void _initControllers() {
    _controllers = List.generate(widget.videoUrls.length, (index) => null);
    _initController(_currentPage);
    if (_currentPage + 1 < widget.videoUrls.length) {
      _initController(_currentPage + 1);
    }
  }

  // 初始化单个视频控制器
  Future<void> _initController(int index) async {
    if (index < 0 || index >= widget.videoUrls.length || _controllers[index] != null) return;

    final controller = VideoPlayerController.networkUrl(
      Uri.parse(widget.videoUrls[index]),
      videoPlayerOptions: VideoPlayerOptions(mixWithOthers: false),
    );

    setState(() {
      _controllers[index] = controller;
    });

    await controller.initialize();
    controller.setLooping(true);
    controller.addListener(() => _checkPreloadNext(index));
  }

  // 触发下一个视频预加载的逻辑
  void _checkPreloadNext(int currentIndex) {
    if (_isLoadingNext) return;
    final controller = _controllers[currentIndex];
    if (controller == null || !controller.value.isPlaying) return;

    final duration = controller.value.duration;
    final position = controller.value.position;
    if (duration == null || position == null) return;

    final remaining = duration - position;
    if (remaining.inSeconds <= 10) {
      final nextIndex = currentIndex + 1;
      if (nextIndex < widget.videoUrls.length && _controllers[nextIndex] == null) {
        _isLoadingNext = true;
        _initController(nextIndex).then((_) {
          setState(() => _isLoadingNext = false);
        });
      }
    }
  }

  // 页面切换时的状态处理
  void _onPageChanged() {
    final newPage = _pageController.page?.round() ?? 0;
    if (newPage == _currentPage) return;

    // 切换播放/暂停状态
    _controllers[_currentPage]?.pause();
    _controllers[newPage]?.play();

    // 预加载下一个视频
    if (newPage + 1 < widget.videoUrls.length && _controllers[newPage + 1] == null) {
      _initController(newPage + 1);
    }

    // 销毁距离当前页面超过2位的控制器,释放资源
    _disposeController(_currentPage - 2);
    _disposeController(newPage + 2);

    setState(() => _currentPage = newPage);
  }

  // 销毁单个控制器
  void _disposeController(int index) {
    if (index < 0 || index >= widget.videoUrls.length) return;
    _controllers[index]?.dispose();
    setState(() => _controllers[index] = null);
  }

  @override
  void dispose() {
    _pageController.dispose();
    for (var controller in _controllers) {
      controller?.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView.builder(
        controller: _pageController,
        scrollDirection: Axis.vertical,
        itemCount: widget.videoUrls.length,
        itemBuilder: (context, index) {
          final controller = _controllers[index];
          if (controller == null || !controller.value.isInitialized) {
            return const Center(child: CircularProgressIndicator());
          }

          return GestureDetector(
            onTap: () => controller.value.isPlaying ? controller.pause() : controller.play(),
            child: Stack(
              fit: StackFit.expand,
              children: [
                VideoPlayer(controller),
                // 可自定义添加点赞、评论等UI组件
                Positioned(
                  bottom: 20,
                  left: 20,
                  child: Text(
                    "视频 ${index + 1}/${widget.videoUrls.length}",
                    style: const TextStyle(color: Colors.white, fontSize: 16),
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

// 使用示例
// void main() {
//   runApp(const MaterialApp(
//     home: ReelsVideoStream(
//       videoUrls: [
//         "https://example.com/video1.mp4",
//         "https://example.com/video2.mp4",
//         // ... 补充剩余18个视频URL
//       ],
//     ),
//   ));
// }

关键细节说明

  • 资源管控:仅保留当前页面及前后1-2个视频的控制器,避免大量未使用的控制器占用内存
  • 预加载时机:通过监听视频播放进度精准触发预加载,确保切换到下一个视频时已完成初始化
  • 无缝体验:页面切换时自动同步播放状态,结合预加载实现无卡顿滑动
  • 交互优化:支持点击切换播放/暂停,视频默认循环播放,符合Reels用户习惯

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 00:22:02