diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart index 374c364f4..55d9a410d 100644 --- a/lib/pages/bangumi/introduction/view.dart +++ b/lib/pages/bangumi/introduction/view.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:PiliPalaX/common/widgets/http_error.dart'; import 'package:PiliPalaX/http/loading_state.dart'; import 'package:flutter/material.dart'; @@ -43,6 +45,7 @@ class _BangumiIntroPanelState extends State late BangumiIntroController bangumiIntroController; late VideoDetailController videoDetailCtr; late int cid; + StreamSubscription? _listener; // 添加页面缓存 @override @@ -55,13 +58,19 @@ class _BangumiIntroPanelState extends State bangumiIntroController = Get.put(BangumiIntroController(), tag: widget.heroTag); videoDetailCtr = Get.find(tag: widget.heroTag); - videoDetailCtr.cid.listen((int p0) { + _listener = videoDetailCtr.cid.listen((int p0) { cid = p0; if (!mounted) return; setState(() {}); }); } + @override + void dispose() { + _listener?.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { super.build(context); @@ -140,6 +149,8 @@ class _BangumiInfoState extends State late final _coinKey = GlobalKey(); late final _favKey = GlobalKey(); + StreamSubscription? _listener; + @override void initState() { super.initState(); @@ -149,13 +160,19 @@ class _BangumiInfoState extends State bangumiItem = bangumiIntroController.bangumiItem; cid = widget.cid!; debugPrint('cid: $cid'); - videoDetailCtr.cid.listen((p0) { + _listener = videoDetailCtr.cid.listen((p0) { cid = p0; if (!mounted) return; setState(() {}); }); } + @override + void dispose() { + _listener?.cancel(); + super.dispose(); + } + // 收藏 showFavBottomSheet() { if (bangumiIntroController.userInfo == null) { diff --git a/lib/pages/bangumi/widgets/bangumi_panel.dart b/lib/pages/bangumi/widgets/bangumi_panel.dart index f980dc688..94ab65e49 100644 --- a/lib/pages/bangumi/widgets/bangumi_panel.dart +++ b/lib/pages/bangumi/widgets/bangumi_panel.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:PiliPalaX/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -41,6 +43,7 @@ class _BangumiPanelState extends State { late int cid; late final VideoDetailController videoDetailCtr; final ItemScrollController itemScrollController = ItemScrollController(); + StreamSubscription? _listener; @override void initState() { @@ -54,7 +57,7 @@ class _BangumiPanelState extends State { } videoDetailCtr = Get.find(tag: widget.heroTag); - videoDetailCtr.cid.listen((int p0) { + _listener = videoDetailCtr.cid.listen((int p0) { cid = p0; currentIndex = widget.pages.indexWhere((EpisodeItem e) => e.cid == cid); if (!mounted) return; @@ -65,6 +68,7 @@ class _BangumiPanelState extends State { @override void dispose() { + _listener?.cancel(); listViewScrollCtr.dispose(); listViewScrollCtr_2.dispose(); super.dispose(); diff --git a/lib/pages/danmaku/view.dart b/lib/pages/danmaku/view.dart index ee20476b5..dc76b1976 100644 --- a/lib/pages/danmaku/view.dart +++ b/lib/pages/danmaku/view.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -33,6 +35,8 @@ class _PlDanmakuState extends State { late bool enableShowDanmaku; int latestAddedPosition = -1; bool? _isFullScreen; + StreamSubscription? _listenerDanmaku; + StreamSubscription? _listenerFS; @override void initState() { @@ -55,14 +59,14 @@ class _PlDanmakuState extends State { ..addStatusLister(playerListener) ..addPositionListener(videoPositionListen); } - playerController.isOpenDanmu.listen((p0) { + _listenerDanmaku = playerController.isOpenDanmu.listen((p0) { if (p0 && !_plDanmakuController.initiated) { _plDanmakuController.initiate( playerController.duration.value.inMilliseconds, playerController.position.value.inMilliseconds); } }); - playerController.isFullScreen.listen((isFullScreen) { + _listenerFS = playerController.isFullScreen.listen((isFullScreen) { if (isFullScreen != _isFullScreen) { _isFullScreen = isFullScreen; if (_controller != null) { @@ -119,6 +123,8 @@ class _PlDanmakuState extends State { @override void dispose() { + _listenerDanmaku?.cancel(); + _listenerFS?.cancel(); playerController.removePositionListener(videoPositionListen); playerController.removeStatusLister(playerListener); _plDanmakuController.dispose(); diff --git a/lib/pages/dynamics/tab/view.dart b/lib/pages/dynamics/tab/view.dart index f74428f8e..ee82ff32a 100644 --- a/lib/pages/dynamics/tab/view.dart +++ b/lib/pages/dynamics/tab/view.dart @@ -33,6 +33,7 @@ class _DynamicsTabPageState extends State late DynamicsTabController _dynamicsTabController; late bool dynamicsWaterfallFlow; late final DynamicsController dynamicsController; + StreamSubscription? _listener; @override bool get wantKeepAlive => true; @@ -61,7 +62,7 @@ class _DynamicsTabPageState extends State searchBarStream.add(false); } }); - dynamicsController.mid.listen((mid) { + _listener = dynamicsController.mid.listen((mid) { // debugPrint('midListen: $mid'); _dynamicsTabController.mid = mid; _dynamicsTabController.scrollController.jumpTo(0); @@ -73,6 +74,7 @@ class _DynamicsTabPageState extends State @override void dispose() { + _listener?.cancel(); _dynamicsTabController.scrollController.removeListener(() {}); dynamicsController.mid.close(); super.dispose(); diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index 4de8e7a4d..83cf4968b 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -40,6 +40,7 @@ class _DynamicsPageState extends State late Future _futureBuilderFutureUp; Box userInfoCache = GStorage.userInfo; late UpPanelPosition upPanelPosition; + StreamSubscription? _listener; @override bool get wantKeepAlive => true; @@ -91,7 +92,7 @@ class _DynamicsPageState extends State // .onSelectType(_dynamicsController.tabController.index); // } // }); - _dynamicsController.userLogin.listen((status) { + _listener = _dynamicsController.userLogin.listen((status) { if (mounted) { setState(() { _futureBuilderFutureUp = _dynamicsController.queryFollowUp(); @@ -118,6 +119,7 @@ class _DynamicsPageState extends State @override void dispose() { + _listener?.cancel(); _dynamicsController.tabController.removeListener(() {}); _dynamicsController.scrollController.removeListener(() {}); super.dispose(); diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index afde29d74..9e22616db 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:PiliPalaX/http/live.dart'; @@ -39,6 +40,7 @@ class _LiveRoomPageState extends State late final _isLogin = GStorage.userInfo.get('userInfoCache') != null; late final _node = FocusNode(); late final _ctr = TextEditingController(); + StreamSubscription? _listener; int latestAddedPosition = -1; bool? _isFullScreen; @@ -61,7 +63,7 @@ class _LiveRoomPageState extends State _futureBuilderFuture = _liveRoomController.queryLiveInfo(); plPlayerController.autoEnterFullscreen(); _liveRoomController.liveMsg(); - plPlayerController.isFullScreen.listen((isFullScreen) { + _listener = plPlayerController.isFullScreen.listen((isFullScreen) { if (isFullScreen != _isFullScreen) { _isFullScreen = isFullScreen; _updateFontSize(); @@ -101,6 +103,7 @@ class _LiveRoomPageState extends State @override void dispose() { + _listener?.cancel(); WidgetsBinding.instance.removeObserver(this); ScreenBrightness().resetApplicationScreenBrightness(); PlPlayerController.setPlayCallBack(null); diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart index 32c3318f2..fc730fe3a 100644 --- a/lib/pages/media/view.dart +++ b/lib/pages/media/view.dart @@ -21,6 +21,7 @@ class MediaPage extends StatefulWidget { class _MediaPageState extends State with AutomaticKeepAliveClientMixin { late MediaController mediaController; + StreamSubscription? _listener; @override bool get wantKeepAlive => true; @@ -32,7 +33,7 @@ class _MediaPageState extends State StreamController mainStream = Get.find().bottomBarStream; - mediaController.userLogin.listen((status) { + _listener = mediaController.userLogin.listen((status) { mediaController.onReload(); }); mediaController.scrollController.addListener( @@ -50,6 +51,7 @@ class _MediaPageState extends State @override void dispose() { + _listener?.cancel(); mediaController.scrollController.removeListener(() {}); super.dispose(); } diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index 2245a26d7..b0c13354d 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:PiliPalaX/utils/storage.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -19,13 +21,14 @@ class _MinePageState extends State { final MineController mineController = Get.put(MineController()) ..themeType.value = ThemeType.values[GStorage.themeType]; late Future _futureBuilderFuture; + StreamSubscription? _listener; @override void initState() { super.initState(); _futureBuilderFuture = mineController.queryUserInfo(); - mineController.userLogin.listen((status) { + _listener = mineController.userLogin.listen((status) { if (mounted) { _futureBuilderFuture = mineController.queryUserInfo(); _futureBuilderFuture.then((value) => setState(() {})); @@ -33,6 +36,12 @@ class _MinePageState extends State { }); } + @override + void dispose() { + _listener?.cancel(); + super.dispose(); + } + Widget get _header => FittedBox( child: Row( mainAxisSize: MainAxisSize.min, diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 879c60f48..cab588185 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:PiliPalaX/common/widgets/self_sized_horizontal_list.dart'; import 'package:PiliPalaX/pages/search/widgets/search_text.dart'; import 'package:PiliPalaX/utils/extension.dart'; @@ -50,7 +52,7 @@ class _VideoIntroPanelState extends State late String heroTag; late VideoIntroController videoIntroController; VideoDetailData? videoDetail; - // late Future? _futureBuilderFuture; + StreamSubscription? _listener; // 添加页面缓存 @override @@ -68,11 +70,17 @@ class _VideoIntroPanelState extends State videoIntroController = Get.put(VideoIntroController(), tag: heroTag) ..heroTag = heroTag; // _futureBuilderFuture = videoIntroController.queryVideoIntro(); - videoIntroController.videoDetail.listen((value) { + _listener = videoIntroController.videoDetail.listen((value) { videoDetail = value; }); } + @override + void dispose() { + _listener?.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { super.build(context); diff --git a/lib/pages/video/detail/introduction/widgets/page.dart b/lib/pages/video/detail/introduction/widgets/page.dart index 2cdfc2e50..ac22adf6d 100644 --- a/lib/pages/video/detail/introduction/widgets/page.dart +++ b/lib/pages/video/detail/introduction/widgets/page.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; @@ -35,6 +36,7 @@ class _PagesPanelState extends State { late final String heroTag; late VideoDetailController _videoDetailController; final ScrollController _scrollController = ScrollController(); + StreamSubscription? _listener; @override void initState() { @@ -43,7 +45,7 @@ class _PagesPanelState extends State { heroTag = widget.heroTag; _videoDetailController = Get.find(tag: heroTag); pageIndex = widget.pages.indexWhere((Part e) => e.cid == cid); - _videoDetailController.cid.listen((int p0) { + _listener = _videoDetailController.cid.listen((int p0) { cid = p0; pageIndex = max(0, widget.pages.indexWhere((Part e) => e.cid == cid)); if (!mounted) return; @@ -61,6 +63,7 @@ class _PagesPanelState extends State { @override void dispose() { + _listener?.cancel(); _scrollController.dispose(); super.dispose(); } diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 43cd689a2..261b49d01 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -98,6 +98,10 @@ class _VideoDetailPageState extends State context.orientation == Orientation.landscape && videoDetailController.horizontalSeasonPanel; + StreamSubscription? _listenerDetail; + StreamSubscription? _listenerLoadingState; + StreamSubscription? _listenerCid; + @override void initState() { super.initState(); @@ -114,14 +118,15 @@ class _VideoDetailPageState extends State tag: heroTag); } videoIntroController = Get.put(VideoIntroController(), tag: heroTag); - videoIntroController.videoDetail.listen((value) { + _listenerDetail = videoIntroController.videoDetail.listen((value) { if (!context.mounted) return; videoPlayerServiceHandler.onVideoDetailChange( value, videoDetailController.cid.value); }); if (videoDetailController.videoType == SearchType.media_bangumi) { bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag); - bangumiIntroController.loadingState.listen((value) { + _listenerLoadingState = + bangumiIntroController.loadingState.listen((value) { if (!context.mounted) return; if (value is Success) { videoPlayerServiceHandler.onVideoDetailChange( @@ -130,7 +135,7 @@ class _VideoDetailPageState extends State ); } }); - videoDetailController.cid.listen((p0) { + _listenerCid = videoDetailController.cid.listen((p0) { if (!context.mounted) return; if (bangumiIntroController.loadingState.value is Success) { videoPlayerServiceHandler.onVideoDetailChange( @@ -323,6 +328,9 @@ class _VideoDetailPageState extends State @override void dispose() { + _listenerDetail?.cancel(); + _listenerLoadingState?.cancel(); + _listenerCid?.cancel(); WidgetsBinding.instance.removeObserver(this); if (!Get.previousRoute.startsWith('/video')) { ScreenBrightness().resetApplicationScreenBrightness(); diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index e1107e796..9e53229f9 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -681,115 +681,113 @@ class PlPlayerController { /// 播放事件监听 void startListeners() { - subscriptions.addAll( - [ - videoPlayerController!.stream.playing.listen((event) { - if (event) { - playerStatus.status.value = PlayerStatus.playing; - } else { - playerStatus.status.value = PlayerStatus.paused; - } - videoPlayerServiceHandler.onStatusChange( - playerStatus.status.value, isBuffering.value); + subscriptions = [ + videoPlayerController!.stream.playing.listen((event) { + if (event) { + playerStatus.status.value = PlayerStatus.playing; + } else { + playerStatus.status.value = PlayerStatus.paused; + } + videoPlayerServiceHandler.onStatusChange( + playerStatus.status.value, isBuffering.value); + + /// 触发回调事件 + for (var element in _statusListeners) { + element(event ? PlayerStatus.playing : PlayerStatus.paused); + } + if (videoPlayerController!.state.position.inSeconds != 0) { + makeHeartBeat(positionSeconds.value, type: 'status'); + } + }), + videoPlayerController!.stream.completed.listen((event) { + if (event) { + playerStatus.status.value = PlayerStatus.completed; /// 触发回调事件 for (var element in _statusListeners) { - element(event ? PlayerStatus.playing : PlayerStatus.paused); + element(PlayerStatus.completed); } - if (videoPlayerController!.state.position.inSeconds != 0) { - makeHeartBeat(positionSeconds.value, type: 'status'); - } - }), - videoPlayerController!.stream.completed.listen((event) { - if (event) { - playerStatus.status.value = PlayerStatus.completed; + } else { + // playerStatus.status.value = PlayerStatus.playing; + } + makeHeartBeat(positionSeconds.value, type: 'completed'); + }), + videoPlayerController!.stream.position.listen((event) { + _position.value = event; + updatePositionSecond(); + if (!isSliderMoving.value) { + _sliderPosition.value = event; + updateSliderPositionSecond(); + } - /// 触发回调事件 - for (var element in _statusListeners) { - element(PlayerStatus.completed); - } - } else { - // playerStatus.status.value = PlayerStatus.playing; - } - makeHeartBeat(positionSeconds.value, type: 'completed'); - }), - videoPlayerController!.stream.position.listen((event) { - _position.value = event; - updatePositionSecond(); - if (!isSliderMoving.value) { - _sliderPosition.value = event; - updateSliderPositionSecond(); - } - - /// 触发回调事件 - for (var element in _positionListeners) { - element(event); - } - makeHeartBeat(event.inSeconds); - }), - videoPlayerController!.stream.duration.listen((Duration event) { - duration.value = event; - }), - videoPlayerController!.stream.buffer.listen((Duration event) { - _buffered.value = event; - updateBufferedSecond(); - }), - videoPlayerController!.stream.buffering.listen((bool event) { - isBuffering.value = event; - videoPlayerServiceHandler.onStatusChange( - playerStatus.status.value, event); - }), - // videoPlayerController!.stream.log.listen((event) { - // debugPrint('videoPlayerController!.stream.log.listen'); - // debugPrint(event); - // SmartDialog.showToast('视频加载日志: $event'); - // }), - videoPlayerController!.stream.error.listen((String event) { - // 直播的错误提示没有参考价值,均不予显示 - if (videoType.value == 'live') return; - if (event.startsWith("Failed to open https://") || - event.startsWith("Can not open external file https://") || - //tcp: ffurl_read returned 0xdfb9b0bb - //tcp: ffurl_read returned 0xffffff99 - event.startsWith('tcp: ffurl_read returned ')) { - EasyThrottle.throttle('videoPlayerController!.stream.error.listen', - const Duration(milliseconds: 10000), () { - Future.delayed(const Duration(milliseconds: 3000), () { - debugPrint("isBuffering.value: ${isBuffering.value}"); - debugPrint("_buffered.value: ${_buffered.value}"); - if (isBuffering.value && _buffered.value == Duration.zero) { - refreshPlayer(); - SmartDialog.showToast('视频链接打开失败,重试中', - displayTime: const Duration(milliseconds: 500)); - } - }); + /// 触发回调事件 + for (var element in _positionListeners) { + element(event); + } + makeHeartBeat(event.inSeconds); + }), + videoPlayerController!.stream.duration.listen((Duration event) { + duration.value = event; + }), + videoPlayerController!.stream.buffer.listen((Duration event) { + _buffered.value = event; + updateBufferedSecond(); + }), + videoPlayerController!.stream.buffering.listen((bool event) { + isBuffering.value = event; + videoPlayerServiceHandler.onStatusChange( + playerStatus.status.value, event); + }), + // videoPlayerController!.stream.log.listen((event) { + // debugPrint('videoPlayerController!.stream.log.listen'); + // debugPrint(event); + // SmartDialog.showToast('视频加载日志: $event'); + // }), + videoPlayerController!.stream.error.listen((String event) { + // 直播的错误提示没有参考价值,均不予显示 + if (videoType.value == 'live') return; + if (event.startsWith("Failed to open https://") || + event.startsWith("Can not open external file https://") || + //tcp: ffurl_read returned 0xdfb9b0bb + //tcp: ffurl_read returned 0xffffff99 + event.startsWith('tcp: ffurl_read returned ')) { + EasyThrottle.throttle('videoPlayerController!.stream.error.listen', + const Duration(milliseconds: 10000), () { + Future.delayed(const Duration(milliseconds: 3000), () { + debugPrint("isBuffering.value: ${isBuffering.value}"); + debugPrint("_buffered.value: ${_buffered.value}"); + if (isBuffering.value && _buffered.value == Duration.zero) { + refreshPlayer(); + SmartDialog.showToast('视频链接打开失败,重试中', + displayTime: const Duration(milliseconds: 500)); + } }); - return; - } - debugPrint('videoPlayerController!.stream.error.listen: '); - if (event.startsWith('Could not open codec')) { - SmartDialog.showToast('无法加载解码器, $event,可能会切换至软解'); - return; - } - SmartDialog.showToast('视频加载错误, $event'); - }), - // videoPlayerController!.stream.volume.listen((event) { - // if (!mute.value && _volumeBeforeMute != event) { - // _volumeBeforeMute = event / 100; - // } - // }), - // 媒体通知监听 - onPlayerStatusChanged.listen((PlayerStatus event) { - videoPlayerServiceHandler.onStatusChange(event, isBuffering.value); - }), - onPositionChanged.listen((Duration event) { - EasyThrottle.throttle( - 'mediaServicePosition', - const Duration(seconds: 1), - () => videoPlayerServiceHandler.onPositionChange(event)); - }), - ], - ); + }); + return; + } + debugPrint('videoPlayerController!.stream.error.listen: '); + if (event.startsWith('Could not open codec')) { + SmartDialog.showToast('无法加载解码器, $event,可能会切换至软解'); + return; + } + SmartDialog.showToast('视频加载错误, $event'); + }), + // videoPlayerController!.stream.volume.listen((event) { + // if (!mute.value && _volumeBeforeMute != event) { + // _volumeBeforeMute = event / 100; + // } + // }), + // 媒体通知监听 + onPlayerStatusChanged.listen((PlayerStatus event) { + videoPlayerServiceHandler.onStatusChange(event, isBuffering.value); + }), + onPositionChanged.listen((Duration event) { + EasyThrottle.throttle( + 'mediaServicePosition', + const Duration(seconds: 1), + () => videoPlayerServiceHandler.onPositionChange(event)); + }), + ]; } /// 移除事件监听 diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 1f4701b35..ec6ad1cea 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -146,6 +146,9 @@ class _PLVideoPlayerState extends State } } + StreamSubscription? _listener; + StreamSubscription? _listenerFS; + @override void initState() { super.initState(); @@ -178,7 +181,7 @@ class _PLVideoPlayerState extends State Future.microtask(() async { try { _brightnessValue.value = await ScreenBrightness().application; - ScreenBrightness() + _listener = ScreenBrightness() .onApplicationScreenBrightnessChanged .listen((double value) { if (mounted) { @@ -222,6 +225,8 @@ class _PLVideoPlayerState extends State @override void dispose() { + _listener?.cancel(); + _listenerFS?.cancel(); animationController.dispose(); FlutterVolumeController.removeListener(); super.dispose(); @@ -578,15 +583,13 @@ class _PLVideoPlayerState extends State @override Widget build(BuildContext context) { - if (isFullScreen) { - plPlayerController.subtitleFontScaleFS.listen((value) { - _updateSubtitle(value); - }); - } else { - plPlayerController.subtitleFontScale.listen((value) { - _updateSubtitle(value); - }); - } + _listenerFS = isFullScreen + ? plPlayerController.subtitleFontScaleFS.listen((value) { + _updateSubtitle(value); + }) + : _listenerFS = plPlayerController.subtitleFontScale.listen((value) { + _updateSubtitle(value); + }); final Color colorTheme = Theme.of(context).colorScheme.primary; const TextStyle textStyle = TextStyle( color: Colors.white,