Flutter使用video_compress二次压缩报错:Bad state: Stream已被监听
问题:Flutter中使用video_compress二次压缩时抛出
Bad state: Stream has already been listened to.错误 我在Flutter应用里用video_compress包实现视频压缩功能,流程是用户选完视频后,跳转下一页前先执行压缩,压缩时显示带进度的对话框。第一次压缩正常,进度对话框也能正确显示,但第二次及之后压缩会抛出Bad state: Stream has already been listened to.错误,求问题原因及解决办法。
GalleryApi类代码
class GalleryApi { static Future<MediaInfo?> videoCompress(List<File?> file) async { try { await VideoCompress.setLogLevel(0); return VideoCompress.compressVideo(file.last!.path, quality: VideoQuality.LowQuality, includeAudio: true); } catch (e) { VideoCompress.cancelCompression(); } } }
首屏压缩方法代码
Future compressVideo() async { showDialog( context: context, barrierDismissible: false, builder: (context) => const Dialog(child: ProgressDialogWidget()), ); final info = await GalleryApi.videoCompress(selectedAssets.value); Navigator.of(context).pop(); final size = await selectedAssets.value.last!.lengthSync(); final ogSize = (size / 1000000).round(); selectedAssets.value.clear(); selectedAssets.value.add(info!.file); Get.to(ImagePost( images: selectedAssets.value, isVideo: isVideo, ogSize: ogSize.toString(), blurHash: [], )); }
进度对话框代码
class _ProgressDialogWidgetState extends State<ProgressDialogWidget> { late Subscription subscription; double? progress; @override void initState() { super.initState(); subscription = VideoCompress.compressProgress$ .subscribe((progress) => setState(() => this.progress = progress)); } @override void dispose() { VideoCompress.cancelCompression(); subscription.unsubscribe; super.dispose(); } @override Widget build(BuildContext context) { final value = progress == null ? progress : progress! / 100; final _height = MediaQuery.of(context).size.height; final _width = MediaQuery.of(context).size.width; return SizedBox( height: _height * 0.2, width: _width, child: Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Please wait while it's compressing...", style: TextStyle(fontSize: _width * 0.03), ), SizedBox( height: _height * 0.02, ), Row( children: [ Expanded( child: LinearProgressIndicator( value: value, minHeight: _height * 0.015, ), ), ], ), ], ), ), ); } }
问题原因
VideoCompress.compressProgress$是单订阅流(Single-Subscription Stream),这类流只能被监听一次,首次监听完成后流会自动关闭,二次监听时就会触发Bad state错误。- 每次打开
ProgressDialogWidget都会重新订阅该流,且dispose方法中存在语法错误:subscription.unsubscribe缺少括号,导致首次订阅未被正确取消,流资源未释放。
解决方案
修复订阅取消的语法错误
在ProgressDialogWidget的dispose方法中,将subscription.unsubscribe改为subscription.unsubscribe();,确保对话框销毁时正确取消订阅:@override void dispose() { VideoCompress.cancelCompression(); subscription.unsubscribe(); // 补全括号 super.dispose(); }将单订阅流转为多订阅流
使用asBroadcastStream()将compressProgress$转换为广播流,支持多次监听,修改initState中的订阅代码:@override void initState() { super.initState(); subscription = VideoCompress.compressProgress$ .asBroadcastStream() // 转为广播流 .subscribe((progress) => setState(() => this.progress = progress)); }额外优化建议
- 每次压缩开始前,可主动取消之前可能存在的订阅,避免流资源占用;
- 在
GalleryApi.videoCompress方法中,压缩完成或异常时,可调用VideoCompress.cancelCompression()清理流状态,确保下次压缩时流处于可用状态。
内容的提问来源于stack exchange,提问作者Mohammed Bekele
相关产品推荐
相关产品推荐

