Flutter:Chewie与VideoPlayer释放后仍后台播放问题求助
Flutter视频播放器关闭后后台持续播放的问题修复
问题根源分析
- 控制器重复创建且未清理:在
FutureBuilder的builder回调中每次都会创建新的VideoPlayerController和ChewieController实例,但旧实例未被销毁。FutureBuilder会在Widget重建时多次执行builder,导致多个控制器同时运行,即使当前Widget的dispose被调用,之前遗弃的控制器仍在后台播放。 - dispose操作顺序错误:原代码中先调用控制器的
dispose(),再执行pause(),此时控制器已被释放,pause操作完全无效,甚至会触发异常。 - Future未缓存导致重复请求:
fetchJsonContent()未被缓存,每次build都会重新发起网络请求,进一步加剧了控制器重复创建的问题。
修复步骤
- 缓存网络请求Future:在
initState中初始化并缓存视频URL的请求Future,避免重复发起请求。 - 控制器单一实例化:将控制器改为可空类型,添加专用初始化方法,确保整个Widget生命周期内只创建一次控制器。
- 修正dispose执行顺序:先暂停视频播放,再释放控制器资源,确保释放前停止所有播放操作。
- 添加状态校验:在构建UI前校验控制器状态,避免未初始化时的错误。
修改后的完整代码
class VideoPlayerApp extends StatefulWidget { final String jsonLink; final String fileTitle; final String userId; final String creationTime; final bool showOnlyfile; const VideoPlayerApp(this.jsonLink, this.fileTitle, this.userId, this.creationTime, this.showOnlyfile, {super.key}); @override State<VideoPlayerApp> createState() => _VideoPlayerAppState(); } class _VideoPlayerAppState extends State<VideoPlayerApp> with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; String videoRootUrl = ""; VideoPlayerController? videoPlayerController; ChewieController? chewieController; late Future<String> _videoUrlFuture; Future<String> fetchJsonContent() async { final jsonResponse = await http.get(Uri.parse(widget.jsonLink)); if (jsonResponse.statusCode == 200) { var jsonContent = jsonDecode(jsonResponse.body); videoRootUrl = jsonContent["meta"]["rootUrl"]; var files = jsonContent["files"]; if (files.length > 0) { return videoRootUrl + files[0]; } return ""; } else { throw Exception('Failed to load json file of drive'); } } @override void initState() { super.initState(); // 缓存请求Future,避免每次build重复执行网络请求 _videoUrlFuture = fetchJsonContent(); } @override void dispose() { print("|||||||||Video Dispose Called|||||||"); // 先暂停播放,再释放控制器资源 chewieController?.pause(); videoPlayerController?.pause(); chewieController?.dispose(); videoPlayerController?.dispose(); super.dispose(); } // 初始化视频控制器,确保仅执行一次 void _initVideoControllers(String videoUrl) { if (videoPlayerController != null) return; videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(videoUrl)); chewieController = ChewieController( videoPlayerController: videoPlayerController!, autoPlay: false, autoInitialize: true, looping: false, showControls: true, aspectRatio: 16 / 9, materialProgressColors: const ChewieProgressColors( backgroundColor: Color.fromARGB(255, 4, 50, 90), bufferedColor: Color.fromARGB(255, 4, 66, 116), ), errorBuilder: (context, errorMessage) { print("Video Player error --- $errorMessage"); return const Center( child: Icon(Icons.error,color: Colors.white,), ); }, ); } @override Widget build(BuildContext context) { super.build(context); return WillPopScope( onWillPop: ()async{ switch(contentUiPreviousPath){ case "contentList": Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context)=>courseContent(creatorName: currentCourseCreator, courseId: currentCourseId, batchId: currentBatchId, batchTitle: currentBatchTitle, discussionChannel: discussionChannel, announcementChannnel: announcementChannnel, tabBarIndex: 1))); break; default: Navigator.of(context).pop(); break; } return false; }, child: Scaffold( appBar: (widget.showOnlyfile)?null:const CustomAppBar(networkId: 'No-Network',tempNetworkImgUrl: ""), drawer: (widget.showOnlyfile)?null:const CustomDrawer(), body: bindVideoFiles()), ); } Widget bindVideoFiles() { return FutureBuilder( future: _videoUrlFuture, builder: (BuildContext context, AsyncSnapshot<String> snapshot) { if (snapshot.hasData && snapshot.data!.isNotEmpty) { _initVideoControllers(snapshot.data!); // 等待控制器初始化完成 if (videoPlayerController == null || chewieController == null) { return const Center(child: CircularProgressIndicator()); } return SingleChildScrollView( child: Column( children: [ (widget.showOnlyfile)?const SizedBox():Container( padding: const EdgeInsets.symmetric( vertical: 13, ), width: MediaQuery.of(context).size.width * .9, alignment: Alignment.centerLeft, decoration: const BoxDecoration( border: Border( bottom: BorderSide( width: 2, color: Colors.black54, ))), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.fileTitle, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black45, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox( height: 3, ), Text( widget.creationTime, style: const TextStyle( fontSize: 12, color: Colors.black45, ), maxLines: 1, textAlign: TextAlign.left, overflow: TextOverflow.ellipsis, ), const SizedBox( height: 3, ), ], ), ), Container( height: (MediaQuery.of(context).size.width * 9) / 16, margin: const EdgeInsets.symmetric(vertical: 20), decoration: const BoxDecoration(color: Colors.black), child: Chewie( controller: chewieController!, )), ], ), ); } else if (snapshot.hasError) { return const Center(child: Icon(Icons.error, color: Colors.white)); } else { return const Center( child: CircularProgressIndicator(), ); } }, ); } }
关键修改说明
- 缓存Future:
_videoUrlFuture在initState中初始化,确保网络请求仅执行一次。 - 控制器可空化:将
videoPlayerController和chewieController改为可空类型,配合_initVideoControllers方法避免重复创建。 - 修正dispose顺序:先暂停播放再释放控制器,确保资源清理前停止所有播放操作。
- 状态校验:在FutureBuilder中添加控制器非空判断,防止未初始化时的UI错误。
内容的提问来源于stack exchange,提问作者Ashish Tandon
相关产品推荐
相关产品推荐

