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

Flutter Android端video_player后台停止播放及崩溃日志问题

Android平台Flutter video_player后台播放失效问题

问题现象

在Android平台使用Flutter的video_player包播放视频,近期Flutter或该包更新后,切换应用到后台时视频停止播放,日志中出现Surface销毁相关错误信息。

代码示例

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with WidgetsBindingObserver {
  int _counter = 0;
  late VideoPlayerController _controller;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  void initState() {
    super.initState();
    _controller = VideoPlayerController.networkUrl(Uri.parse(
        'https://www.sample-videos.com/video321/mp4/720/big_buck_bunny_720p_20mb.mp4')
        ,videoPlayerOptions: VideoPlayerOptions(
          allowBackgroundPlayback: true, mixWithOthers: true),)
      ..initialize().then((_) {
        setState(() {});
      });
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  Widget build(BuildContext context) {
    
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: _controller.value.isInitialized
            ? AspectRatio(
          aspectRatio: _controller.value.aspectRatio,
          child: VideoPlayer (_controller),
        )
            : Container(),
      ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              _controller.value.isPlaying
                  ? _controller.pause()
                  : _controller.play();
            });
          },
          child: Icon(
            _controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
          ),
        ),
    );
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.paused) {
      print("AppLifecycleState.paused");
    }
  }
}

设备日志

2025-03-13 10:04:21.783  8186-8186  flutter                 com.example.untitled4                I  AppLifecycleState.paused
2025-03-13 10:04:21.784  8186-8186  SurfaceView@6b4b083     com.example.untitled4                D  updateSurface: surface is not valid

2025-03-13 10:04:21.863 14716-15805 WindowManager           system_server                        E  win=Window{fd23590 u0 com.example.untitled4/com.example.untitled4.MainActivity} destroySurfaces: appStopped=true cleanupOnResume=false win.mWindowRemovalAllowed=false win.mRemoveOnExit=false win.mViewVisibility=8 caller=com.android.server.wm.ActivityRecord.destroySurfaces:7033 com.android.server.wm.ActivityRecord.destroySurfaces:7014 com.android.server.wm.ActivityRecord.activityStopped:7747 com.android.server.wm.ActivityClientController.activityStopped:321 android.app.IActivityClientController$Stub.onTransact:702 com.android.server.wm.ActivityClientController.onTransact:186 android.os.Binder.execTransactInternal:1380 
2025-03-13 10:04:21.863 14716-15805 WindowManager           system_server                        I  Destroying surface Surface(name=com.example.untitled4/com.example.untitled4.MainActivity$_8186)/@0x709f3e3 called by com.android.server.wm.WindowStateAnimator.destroySurface:811 com.android.server.wm.WindowStateAnimator.destroySurfaceLocked:478 com.android.server.wm.WindowState.destroySurfaceUnchecked:4219 com.android.server.wm.WindowState.destroySurface:4193 com.android.server.wm.ActivityRecord.destroySurfaces:7033 com.android.server.wm.ActivityRecord.destroySurfaces:7014 com.android.server.wm.ActivityRecord.activityStopped:7747 com.android.server.wm.ActivityClientController.activityStopped:321 

解决方案

1. 配置AndroidManifest.xml

Android 12及以上版本要求后台媒体播放必须使用前台服务,需在android/app/src/main/AndroidManifest.xml中添加权限和服务声明:

<!-- 添加权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

<application ...>
  <!-- 注册媒体播放服务 -->
  <service
      android:name="com.google.android.exoplayer2.ui.PlayerNotificationManager$MediaPlaybackService"
      android:foregroundServiceType="mediaPlayback"
      android:exported="false"/>
</application>

2. 优化代码逻辑

  • 完善生命周期回调,确保后台时音频继续播放:
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
  super.didChangeAppLifecycleState(state);
  if (state == AppLifecycleState.paused) {
    // 后台时若视频正在播放,重新触发播放以保持音频运行
    if (_controller.value.isPlaying) {
      _controller.play();
    }
  } else if (state == AppLifecycleState.resumed) {
    // 回到前台刷新UI恢复视频画面
    setState(() {});
  }
}
  • 添加资源释放逻辑,避免内存泄漏:
@override
void dispose() {
  WidgetsBinding.instance.removeObserver(this);
  _controller.dispose();
  super.dispose();
}

3. 版本适配说明

video_player底层依赖ExoPlayer,近期版本对Android后台播放的权限校验更严格,必须通过前台服务声明才能让系统允许后台媒体播放。日志中的Surface销毁是应用后台时的正常系统行为,配置前台服务后,音频会独立于Surface继续播放。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 23:10:57