Flutter仿Instagram故事切换时视频复用异常问题求助
Flutter仿Instagram故事功能:切换用户后视频重复播放问题解决
问题概述
实现仿Instagram故事功能时,已完成图片和视频故事的基础展示,但存在异常:第一个用户的视频故事播放后,滑动切换到另一个用户的视频故事,暂停再恢复播放时,旧用户的视频会和当前视频一同播放。使用flutter_story插件,尝试销毁VideoPlayerController未解决问题。
问题根源
- 全局的
storyVideoPlayer流被多个StoryChild实例订阅,切换用户后旧实例的订阅未取消,导致播放命令触发所有已订阅视频同步播放。 Story组件的addAutomaticKeepAlives: true设置,导致旧的StoryChild实例未被销毁,资源和订阅残留。- 所有视频共享同一播放控制流,未实现每个视频独立的播放状态管理。
修复方案
1. 让每个视频StoryChild独立管理播放状态
修改StoryChild组件,取消全局流依赖,通过组件可见性和StoryController状态单独控制每个视频的播放:
class _StoryChildState extends State<StoryChild> { StreamSubscription? _storyControllerSubscription; bool _isInitialized = false; VideoPlayerController? videoPlayer; bool _isVisible = false; @override void initState() { super.initState(); if (widget.story?.type == "video") { _initializeVideo(); _listenToStoryController(); } } Future<void> _initializeVideo() async { final file = await VideoCacheManager.getVideoFile(widget.story?.cdnUrl ?? ''); videoPlayer = VideoPlayerController.file(file); await videoPlayer?.initialize().then((value) { if (mounted) { setState(() { _isInitialized = true; }); } // 初始可见时自动播放 if (_isVisible) { videoPlayer?.play(); } }); } void _listenToStoryController() { _storyControllerSubscription = widget.storyController?.playbackStream.listen((state) { if (videoPlayer == null) return; if (state == StoryPlaybackState.play) { if (_isVisible) { videoPlayer?.play(); } } else if (state == StoryPlaybackState.pause) { videoPlayer?.pause(); } }); } void _disposeResources() { videoPlayer?.pause(); videoPlayer?.dispose(); videoPlayer = null; _storyControllerSubscription?.cancel(); _storyControllerSubscription = null; _isInitialized = false; } @override void dispose() { _disposeResources(); super.dispose(); } @override Widget build(BuildContext context) { if (widget.story?.type == "image") { return Stack( alignment: Alignment.center, fit: StackFit.expand, children: [ Positioned.fill(child: Container(color: Colors.transparent)), CachedNetworkImage( imageUrl: widget.story?.cdnUrl ?? '', fit: BoxFit.contain, alignment: Alignment.center, cacheKey: widget.story?.cdnUrl ?? '', cacheManager: CustomCacheManager().getCacheManager(widget.story?.cdnUrl ?? ''), progressIndicatorBuilder: (context, url, progress) => Center( child: CircularProgressIndicator(color: Colors.white), ), ), ], ); } else if (widget.story?.type == "video") { return getContentView(); } else { return Container( decoration: BoxDecoration( color: Color(int.parse(widget.story?.backgroundColor ?? '')), ), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), ); } } Widget getContentView() { if (_isInitialized) { return VisibilityDetector( key: Key(widget.story?.cdnUrl ?? ''), onVisibilityChanged: (info) { setState(() { _isVisible = info.visibleFraction == 1; }); if (videoPlayer == null) return; if (_isVisible) { if (widget.storyController?.isPlaying ?? false) { videoPlayer?.play(); } } else { videoPlayer?.pause(); } }, child: Center( child: ClipRRect( borderRadius: BorderRadius.all(Radius.circular(10)), child: AspectRatio( aspectRatio: videoPlayer!.value.aspectRatio, child: VideoPlayer(videoPlayer!), ), ), ), ); } else { return Center( child: SizedBox( width: 70, height: 70, child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation<Color>(Colors.white), strokeWidth: 3, ), ), ); } } }
2. 关闭父组件的自动保活
修改Story组件的addAutomaticKeepAlives为false,确保切换用户时旧组件被销毁:
Story( sortByVisited: true, shrinkWrap: true, autoplay: true, physics: NeverScrollableScrollPhysics(), controller: controller.storyController, scrollDirection: Axis.horizontal, addAutomaticKeepAlives: false, // 修改此处 addRepaintBoundaries: false, // 其他属性保持不变 )
3. 简化StoryCard的暂停/恢复逻辑
不再依赖全局storyVideoPlayer,直接通过StoryController控制:
onPause: (value) { controller.storyController.pauseStory(); }, onResume: (value) { controller.storyController.playStory(); },
修复原理
- 每个视频独立创建
VideoPlayerController,避免实例共享冲突。 - 通过
VisibilityDetector监听组件可见性,仅当前可见的视频响应播放命令,不可见时自动暂停。 - 取消全局流的多订阅问题,改为监听StoryController状态,确保播放操作仅作用于当前可见视频。
- 关闭自动保活,确保切换用户时旧组件资源被彻底释放。
内容的提问来源于stack exchange,提问作者Hasmukh Kachhatiya
相关产品推荐
相关产品推荐

