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

Flutter Web在Safari中视频表现异常:已暂停仍播放或无法播放

Flutter Web Safari多视频播放异常问题

问题现象

  • 在Safari浏览器中使用Flutter Web加载多个VideoWidget时,第一个视频需用户交互才能播放(符合浏览器自动播放策略),但切换到第二个VideoWidget时:
    • 尽管在initState中调用了_controller.pause(),视频仍自动播放,导致按钮显示播放图标但视频实际正在播放
    • 第二个视频的控制器监听器videoListener完全不触发,直到手动点击播放按钮后才开始执行print语句

问题根源

  1. Safari自动播放策略与初始化时机冲突:Safari对媒体自动播放限制严格,但TabBarView会预加载第二个组件,video_player插件初始化时可能触发意外播放,提前调用的pause()无法覆盖该行为
  2. 监听器绑定时机错误:在视频未完成初始化时就添加监听器,导致监听器无法正确捕获后续状态变更
  3. UI状态同步缺失:视频状态变化时未触发setState(),造成按钮图标与实际播放状态不一致

修复方案

针对上述问题,对VideoWidget的状态类做以下修改:

  1. 延迟暂停操作到视频初始化完成后执行,确保覆盖自动播放行为
  2. 初始化完成后再绑定监听器,保证监听器能正常捕获状态变化
  3. 在监听器中调用setState(),同步UI与视频状态
  4. 监听组件可见性变化,切换到前台时强制暂停视频,解决预加载导致的自动播放

修改后的完整代码

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

void main() => runApp(const VideoDemo());

class VideoDemo extends StatelessWidget {
  const VideoDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 2,
        child: Scaffold(
          appBar: AppBar(
            bottom: const TabBar(
              tabs: [
                Tab(icon: Icon(Icons.first_page)),
                Tab(icon: Icon(Icons.last_page)),
              ],
            ),
            title: const Text('Video Demo'),
          ),
          body: const TabBarView(
            children: [
              VideoWidget(),
              VideoWidget(),
            ],
          ),
        ),
      ),
    );
  }
}

class VideoWidget extends StatefulWidget {
  const VideoWidget({Key? key}) : super(key: key);

  @override
  _VideoWidgetState createState() => _VideoWidgetState();
}

class _VideoWidgetState extends State<VideoWidget> with WidgetsBindingObserver {
  late VideoPlayerController _controller;
  bool hasLoaded = false;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _initVideoController();
  }

  void _initVideoController() async {
    _controller = VideoPlayerController.network(
        'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4')
      ..setLooping(true);

    await _controller.initialize();
    // 初始化完成后再暂停,确保覆盖自动播放
    await _controller.pause();
    // 初始化完成后添加监听器
    _controller.addListener(videoListener);

    setState(() {
      hasLoaded = true;
    });
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    // 组件切回前台时强制暂停视频
    if (state == AppLifecycleState.resumed && hasLoaded) {
      _controller.pause();
      setState(() {});
    }
  }

  @override
  Widget build(BuildContext context) => Container(
      width: MediaQuery.of(context).size.width,
      height: MediaQuery.of(context).size.height,
      color: Colors.red,
      child: Stack(
        alignment: Alignment.center,
        children: [
          _controller.value.isInitialized
              ? VideoPlayer(_controller)
              : const Center(child: CircularProgressIndicator()),
          SizedBox(
              height: 50,
              width: 130,
              child: ElevatedButton(
                onPressed: () {
                  _controller.value.isPlaying
                      ? _controller.pause()
                      : _controller.play();
                  setState(() {});
                },
                child: Icon(
                  _controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
                ),
              )),
        ],
      ));

  void videoListener() {
    if (hasLoaded) {
      print("isPlaying: ${_controller.value.isPlaying}");
      // 监听器触发时同步UI状态
      setState(() {});
    }
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _controller.removeListener(videoListener);
    _controller.dispose();
    super.dispose();
  }
}

关键修改说明

  • 延迟操作时机:将pause()和监听器绑定移到initialize()完成后,确保视频状态稳定后再执行
  • 监听组件可见性:通过WidgetsBindingObserver捕获组件切换事件,强制暂停视频解决预加载问题
  • 同步UI状态:监听器中调用setState(),保证按钮图标与视频播放状态实时一致
  • 优化资源管理:dispose时移除监听器和生命周期观察者,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 16:55:23