diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 6a218596..d5ab79ff 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -50,6 +50,10 @@ class VideoDetailController extends GetxController Box user = GStrorage.user; Box localCache = GStrorage.localCache; PlPlayerController plPlayerController = PlPlayerController(); + // 是否开始自动播放 存在多p的情况下,第二p需要为true + RxBool autoPlay = true.obs; + // 视频资源是否有效 + RxBool isEffective = true.obs; @override void onInit() { @@ -130,12 +134,17 @@ class VideoDetailController extends GetxController ), // 硬解 enableHA: true, - autoplay: true, + autoplay: autoPlay.value, seekTo: defaultST, duration: Duration(milliseconds: duration), ); } + // 手动点击播放 + handlePlay() { + plPlayerController.togglePlay(); + } + // 视频链接 queryVideoUrl() async { var result = await VideoHttp.videoUrl(cid: cid, bvid: bvid); diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 64e14db4..68edd92a 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -55,7 +55,11 @@ class _VideoDetailPageState extends State isPlay = true; setState(() {}); // 播放完成停止 or 切换下一个 - if (status == PlayerStatus.completed) {} + if (status == PlayerStatus.completed) { + // 当只有1p或多p未打开自动播放时,播放完成还原进度条,展示控制栏 + plPlayerController!.seekTo(Duration.zero); + plPlayerController!.onLockControl(false); + } } }, ); @@ -180,6 +184,38 @@ class _VideoDetailPageState extends State ), ), ), + + /// 关闭自动播放时 手动播放 + Obx( + () => Visibility( + visible: isShowCover && + videoDetailController + .isEffective.value && + !videoDetailController.autoPlay.value, + child: Positioned( + right: 12, + bottom: 6, + child: TextButton.icon( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty + .resolveWith((states) { + return Theme.of(context) + .colorScheme + .primaryContainer; + }), + ), + onPressed: () => videoDetailController + .handlePlay(), + icon: const Icon( + Icons.play_circle_outline, + size: 20, + ), + label: const Text('Play'), + ), + ), + ), + ), ], ), ); diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index af44d4fc..8f41c674 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -39,6 +39,7 @@ class PlPlayerController { // 播放位置 final Rx _position = Rx(Duration.zero); final Rx _sliderPosition = Rx(Duration.zero); + final Rx _sliderTempPosition = Rx(Duration.zero); final Rx _duration = Rx(Duration.zero); final Rx _buffered = Rx(Duration.zero); @@ -51,13 +52,13 @@ class PlPlayerController { final Rx _showVolumeStatus = false.obs; final Rx _showBrightnessStatus = false.obs; final Rx _doubleSpeedStatus = false.obs; - final Rx _controlsClose = false.obs; + final Rx _controlsLock = false.obs; Rx videoFitChanged = false.obs; final Rx _videoFit = Rx(BoxFit.fill); /// - bool _isSliderMoving = false; + Rx _isSliderMoving = false.obs; PlaylistMode _looping = PlaylistMode.none; bool _autoPlay = false; final bool _listenersInitialized = false; @@ -112,10 +113,15 @@ class PlPlayerController { /// [videoController] instace of Player VideoController? get videoController => _videoController; + Rx get isSliderMoving => _isSliderMoving; + /// 进度条位置及监听 Rx get sliderPosition => _sliderPosition; Stream get onSliderPositionChanged => _sliderPosition.stream; + Rx get sliderTempPosition => _sliderTempPosition; + // Stream get onSliderPositionChanged => _sliderPosition.stream; + /// 是否展示控制条及监听 Rx get showControls => _showControls; Stream get onShowControlsChanged => _showControls.stream; @@ -151,9 +157,11 @@ class PlPlayerController { Rx isBuffering = true.obs; - Rx get controlsClose => _controlsClose; + /// 屏幕锁 为true时,关闭控制栏 + Rx get controlsLock => _controlsLock; PlPlayerController({ + // 直播间 传false 关闭控制栏 this.controlsEnabled = true, this.fits = const [ BoxFit.contain, @@ -349,7 +357,7 @@ class PlPlayerController { }), videoPlayerController!.stream.position.listen((event) { _position.value = event; - if (!_isSliderMoving) { + if (!isSliderMoving.value) { _sliderPosition.value = event; } }), @@ -387,8 +395,6 @@ class PlPlayerController { position = Duration.zero; } _position.value = position; - print('seek 🌹duration : ${duration.value.inSeconds}'); - if (duration.value.inSeconds != 0) { // await _videoPlayerController!.stream.buffer.first; await _videoPlayerController?.seek(position); @@ -396,13 +402,11 @@ class PlPlayerController { // play(); // } } else { - print('🌹🌹'); _timerForSeek?.cancel(); _timerForSeek = Timer.periodic(const Duration(milliseconds: 200), (Timer t) async { //_timerForSeek = null; if (duration.value.inSeconds != 0) { - print('🌹🌹🌹'); await _videoPlayerController?.seek(position); // if (playerStatus.stopped) { // play(); @@ -471,7 +475,7 @@ class PlPlayerController { /// 隐藏控制条 void _hideTaskControls() { _timer = Timer(const Duration(milliseconds: 3000), () { - if (!_isSliderMoving) { + if (!isSliderMoving.value) { controls = false; } _timer = null; @@ -485,11 +489,15 @@ class PlPlayerController { void onChangedSliderStart() { feedBack(); - _isSliderMoving = true; + _isSliderMoving.value = true; + } + + void onUodatedSliderProgress(value) { + _sliderTempPosition.value = value; } void onChangedSliderEnd() { - _isSliderMoving = false; + _isSliderMoving.value = false; _hideTaskControls(); } @@ -610,9 +618,9 @@ class PlPlayerController { } /// 关闭控制栏 - void onCloseControl(bool val) { + void onLockControl(bool val) { feedBack(); - _controlsClose.value = val; + _controlsLock.value = val; showControls.value = !val; } @@ -642,10 +650,12 @@ class PlPlayerController { _position.close(); _playerEventSubs?.cancel(); _sliderPosition.close(); + _sliderTempPosition.close(); + _isSliderMoving.close(); _duration.close(); _buffered.close(); _showControls.close(); - _controlsClose.close(); + _controlsLock.close(); playerStatus.status.close(); dataStatus.status.close(); diff --git a/lib/plugin/pl_player/models/duration.dart b/lib/plugin/pl_player/models/duration.dart new file mode 100644 index 00000000..3ce3cc47 --- /dev/null +++ b/lib/plugin/pl_player/models/duration.dart @@ -0,0 +1,29 @@ +extension DurationExtension on Duration { + /// Returns clamp of [Duration] between [min] and [max]. + Duration clamp(Duration min, Duration max) { + if (this < min) return min; + if (this > max) return max; + return this; + } + + /// Returns a [String] representation of [Duration]. + String label({Duration? reference}) { + reference ??= this; + if (reference > const Duration(days: 1)) { + final days = inDays.toString().padLeft(3, '0'); + final hours = (inHours - (inDays * 24)).toString().padLeft(2, '0'); + final minutes = (inMinutes - (inHours * 60)).toString().padLeft(2, '0'); + final seconds = (inSeconds - (inMinutes * 60)).toString().padLeft(2, '0'); + return '$days:$hours:$minutes:$seconds'; + } else if (reference > const Duration(hours: 1)) { + final hours = inHours.toString().padLeft(2, '0'); + final minutes = (inMinutes - (inHours * 60)).toString().padLeft(2, '0'); + final seconds = (inSeconds - (inMinutes * 60)).toString().padLeft(2, '0'); + return '$hours:$minutes:$seconds'; + } else { + final minutes = inMinutes.toString().padLeft(2, '0'); + final seconds = (inSeconds - (inMinutes * 60)).toString().padLeft(2, '0'); + return '$minutes:$seconds'; + } + } +} diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 5709007c..0dac67eb 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -2,14 +2,20 @@ import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; +import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:pilipala/common/widgets/app_bar_ani.dart'; import 'package:pilipala/plugin/pl_player/controller.dart'; +import 'package:pilipala/plugin/pl_player/models/duration.dart'; import 'package:pilipala/plugin/pl_player/models/play_status.dart'; +import 'package:pilipala/plugin/pl_player/utils.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'widgets/backward_seek.dart'; import 'widgets/bottom_control.dart'; import 'widgets/common_btn.dart'; +import 'widgets/forward_seek.dart'; +import 'widgets/play_pause_btn.dart'; class PLVideoPlayer extends StatefulWidget { final PlPlayerController controller; @@ -32,6 +38,23 @@ class _PLVideoPlayerState extends State late AnimationController animationController; late VideoController videoController; + bool _mountSeekBackwardButton = false; + bool _mountSeekForwardButton = false; + bool _hideSeekBackwardButton = false; + bool _hideSeekForwardButton = false; + + void onDoubleTapSeekBackward() { + setState(() { + _mountSeekBackwardButton = true; + }); + } + + void onDoubleTapSeekForward() { + setState(() { + _mountSeekForwardButton = true; + }); + } + @override void initState() { super.initState(); @@ -59,10 +82,33 @@ class _PLVideoPlayerState extends State fontWeight: FontWeight.normal, backgroundColor: Color(0xaa000000), ); + const textStyle = TextStyle( + color: Colors.white, + fontSize: 12, + ); return Stack( clipBehavior: Clip.hardEdge, fit: StackFit.passthrough, children: [ + // Wrap [Video] widget with [MaterialVideoControlsTheme]. + // MaterialVideoControlsTheme( + // normal: MaterialVideoControlsThemeData( + // // Modify theme options: + // buttonBarButtonSize: 24.0, + // buttonBarButtonColor: Colors.white, + // ), + // fullscreen: const MaterialVideoControlsThemeData( + // // Modify theme options: + // displaySeekBar: false, + // automaticallyImplySkipNextButton: false, + // automaticallyImplySkipPreviousButton: false, + // ), + // child: Scaffold( + // body: Video( + // controller: videoController, + // ), + // ), + // ), Video( controller: videoController, controls: NoVideoControls, @@ -78,11 +124,29 @@ class _PLVideoPlayerState extends State onTap: () { _.controls = !_.showControls.value; }, - onDoubleTap: () { - if (_.playerStatus.status.value == PlayerStatus.playing) { - _.togglePlay(); + // onDoubleTap: () { + // if (_.playerStatus.status.value == PlayerStatus.playing) { + // _.togglePlay(); + // } else { + // _.play(); + // } + // }, + onDoubleTapDown: (details) { + final totalWidth = MediaQuery.of(context).size.width; + final tapPosition = details.localPosition.dx; + final sectionWidth = totalWidth / 3; + if (tapPosition < sectionWidth) { + // 双击左边区域 👈 + onDoubleTapSeekBackward(); + } else if (tapPosition < sectionWidth * 2) { + if (_.playerStatus.status.value == PlayerStatus.playing) { + _.togglePlay(); + } else { + _.play(); + } } else { - _.play(); + // 双击右边区域 👈 + onDoubleTapSeekForward(); } }, onLongPressStart: (detail) { @@ -112,7 +176,7 @@ class _PLVideoPlayerState extends State clipBehavior: Clip.hardEdge, child: AppBarAni( controller: animationController, - visible: !_.controlsClose.value && _.showControls.value, + visible: !_.controlsLock.value && _.showControls.value, position: 'top', child: widget.headerControl!, ), @@ -122,7 +186,7 @@ class _PLVideoPlayerState extends State clipBehavior: Clip.hardEdge, child: AppBarAni( controller: animationController, - visible: !_.controlsClose.value && _.showControls.value, + visible: !_.controlsLock.value && _.showControls.value, position: 'bottom', child: BottomControl(controller: widget.controller), ), @@ -140,7 +204,7 @@ class _PLVideoPlayerState extends State return Container(); } return Positioned( - bottom: -4, + bottom: -3, left: 0, right: 0, child: SlideTransition( @@ -161,22 +225,22 @@ class _PLVideoPlayerState extends State Theme.of(context).colorScheme.primary.withOpacity(0.4), timeLabelLocation: TimeLabelLocation.none, thumbColor: colorTheme, - barHeight: 3, + barHeight: 2, thumbRadius: 0.0, - onDragStart: (duration) { - _.onChangedSliderStart(); - }, - onDragEnd: () { - _.onChangedSliderEnd(); - }, + // onDragStart: (duration) { + // _.onChangedSliderStart(); + // }, + // onDragEnd: () { + // _.onChangedSliderEnd(); + // }, // onDragUpdate: (details) { // print(details); // }, - onSeek: (duration) { - feedBack(); - _.onChangedSlider(duration.inSeconds.toDouble()); - _.seekTo(duration); - }, + // onSeek: (duration) { + // feedBack(); + // _.onChangedSlider(duration.inSeconds.toDouble()); + // _.seekTo(duration); + // }, )), ); }, @@ -212,13 +276,13 @@ class _PLVideoPlayerState extends State visible: _.showControls.value, child: ComBtn( icon: Icon( - _.controlsClose.value + _.controlsLock.value ? FontAwesomeIcons.lock : FontAwesomeIcons.lockOpen, size: 15, color: Colors.white, ), - fuc: () => _.onCloseControl(!_.controlsClose.value), + fuc: () => _.onLockControl(!_.controlsLock.value), ), ), ), @@ -237,6 +301,141 @@ class _PLVideoPlayerState extends State return Container(); } }), + // 时间进度 + /// TDDO 样式 + Obx( + () => Align( + alignment: Alignment.topCenter, + child: FractionalTranslation( + translation: const Offset(0.0, 2.5), // 上下偏移量(负数向上偏移) + child: Visibility( + visible: _.isSliderMoving.value, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Obx(() { + return Text( + _.sliderTempPosition.value.inMinutes >= 60 + ? printDurationWithHours(_.sliderTempPosition.value) + : printDuration(_.sliderTempPosition.value), + style: textStyle, + ); + }), + const SizedBox(width: 2), + const Text('/', style: textStyle), + const SizedBox(width: 2), + Obx( + () => Text( + _.duration.value.inMinutes >= 60 + ? printDurationWithHours(_.duration.value) + : printDuration(_.duration.value), + style: textStyle, + ), + ), + ], + ), + ), + ), + ), + ), + // 点击 快进/快退 + if (_mountSeekBackwardButton || _mountSeekForwardButton) + Positioned.fill( + child: Row( + children: [ + Expanded( + child: _mountSeekBackwardButton + ? TweenAnimationBuilder( + tween: Tween( + begin: 0.0, + end: _hideSeekBackwardButton ? 0.0 : 1.0, + ), + duration: const Duration(milliseconds: 500), + builder: (context, value, child) => Opacity( + opacity: value, + child: child, + ), + onEnd: () { + if (_hideSeekBackwardButton) { + setState(() { + _hideSeekBackwardButton = false; + _mountSeekBackwardButton = false; + }); + } + }, + child: BackwardSeekIndicator( + onChanged: (value) { + print(value); + // _seekBarDeltaValueNotifier.value = -value; + }, + onSubmitted: (value) { + setState(() { + _hideSeekBackwardButton = true; + }); + Player player = + widget.controller.videoPlayerController!; + var result = player.state.position - value; + result = result.clamp( + Duration.zero, + player.state.duration, + ); + player.seek(result); + widget.controller.play(); + }, + ), + ) + : const SizedBox(), + ), + Expanded( + child: SizedBox( + width: MediaQuery.of(context).size.width / 4, + ), + ), + Expanded( + child: _mountSeekForwardButton + ? TweenAnimationBuilder( + tween: Tween( + begin: 0.0, + end: _hideSeekForwardButton ? 0.0 : 1.0, + ), + duration: const Duration(milliseconds: 500), + builder: (context, value, child) => Opacity( + opacity: value, + child: child, + ), + onEnd: () { + if (_hideSeekForwardButton) { + setState(() { + _hideSeekForwardButton = false; + _mountSeekForwardButton = false; + }); + } + }, + child: ForwardSeekIndicator( + onChanged: (value) { + // _seekBarDeltaValueNotifier.value = value; + }, + onSubmitted: (value) { + setState(() { + _hideSeekForwardButton = true; + }); + Player player = + widget.controller.videoPlayerController!; + var result = player.state.position + value; + result = result.clamp( + Duration.zero, + player.state.duration, + ); + player.seek(result); + widget.controller.play(); + }, + ), + ) + : const SizedBox(), + ), + ], + ), + ), ], ); } @@ -256,5 +455,3 @@ class MSliderTrackShape extends RoundedRectSliderTrackShape { return Rect.fromLTWH(trackLeft, -1, trackWidth, 3); } } - -class PLPlayerCtr extends GetxController {} diff --git a/lib/plugin/pl_player/widgets/backward_seek.dart b/lib/plugin/pl_player/widgets/backward_seek.dart new file mode 100644 index 00000000..35de0ae6 --- /dev/null +++ b/lib/plugin/pl_player/widgets/backward_seek.dart @@ -0,0 +1,84 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class BackwardSeekIndicator extends StatefulWidget { + final void Function(Duration) onChanged; + final void Function(Duration) onSubmitted; + const BackwardSeekIndicator({ + Key? key, + required this.onChanged, + required this.onSubmitted, + }) : super(key: key); + + @override + State createState() => BackwardSeekIndicatorState(); +} + +class BackwardSeekIndicatorState extends State { + Duration value = const Duration(seconds: 10); + + Timer? timer; + + @override + void initState() { + super.initState(); + timer = Timer(const Duration(milliseconds: 400), () { + widget.onSubmitted.call(value); + }); + } + + void increment() { + timer?.cancel(); + timer = Timer(const Duration(milliseconds: 400), () { + widget.onSubmitted.call(value); + }); + widget.onChanged.call(value); + // 重复点击 快退秒数累加10 + setState(() { + value += const Duration(seconds: 10); + }); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0x88767676), + Color(0x00767676), + ], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + ), + child: InkWell( + splashColor: const Color(0x44767676), + onTap: increment, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.fast_rewind, + size: 24.0, + color: Color(0xFFFFFFFF), + ), + const SizedBox(height: 8.0), + Text( + '快退${value.inSeconds}秒', + style: const TextStyle( + fontSize: 12.0, + color: Color(0xFFFFFFFF), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/plugin/pl_player/widgets/bottom_control.dart b/lib/plugin/pl_player/widgets/bottom_control.dart index 4f0c7a75..08a3ef9f 100644 --- a/lib/plugin/pl_player/widgets/bottom_control.dart +++ b/lib/plugin/pl_player/widgets/bottom_control.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; +import 'package:pilipala/plugin/pl_player/widgets/play_pause_btn.dart'; import '../utils.dart'; @@ -56,6 +57,9 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { onDragStart: (duration) { _.onChangedSliderStart(); }, + onDragUpdate: (duration) { + _.onUodatedSliderProgress(duration.timeStamp); + }, onSeek: (duration) { _.onChangedSliderEnd(); _.onChangedSlider(duration.inSeconds.toDouble()); @@ -80,7 +84,10 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { // fuc: () => _.togglePlay(), // ), // ), - // const SizedBox(width: 6), + PlayOrPauseButton( + controller: _, + ), + const SizedBox(width: 4), // 播放时间 Obx(() { return Text( diff --git a/lib/plugin/pl_player/widgets/forward_seek.dart b/lib/plugin/pl_player/widgets/forward_seek.dart new file mode 100644 index 00000000..43ddd322 --- /dev/null +++ b/lib/plugin/pl_player/widgets/forward_seek.dart @@ -0,0 +1,84 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class ForwardSeekIndicator extends StatefulWidget { + final void Function(Duration) onChanged; + final void Function(Duration) onSubmitted; + const ForwardSeekIndicator({ + Key? key, + required this.onChanged, + required this.onSubmitted, + }) : super(key: key); + + @override + State createState() => ForwardSeekIndicatorState(); +} + +class ForwardSeekIndicatorState extends State { + Duration value = const Duration(seconds: 10); + + Timer? timer; + + @override + void initState() { + super.initState(); + timer = Timer(const Duration(milliseconds: 400), () { + widget.onSubmitted.call(value); + }); + } + + void increment() { + timer?.cancel(); + timer = Timer(const Duration(milliseconds: 400), () { + widget.onSubmitted.call(value); + }); + widget.onChanged.call(value); + // 重复点击 快进秒数累加10 + setState(() { + value += const Duration(seconds: 10); + }); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0x00767676), + Color(0x88767676), + ], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + ), + child: InkWell( + splashColor: const Color(0x44767676), + onTap: increment, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + Icons.fast_forward, + size: 24.0, + color: Color(0xFFFFFFFF), + ), + const SizedBox(height: 8.0), + Text( + '快进${value.inSeconds}秒', + style: const TextStyle( + fontSize: 12.0, + color: Color(0xFFFFFFFF), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/plugin/pl_player/widgets/play_pause_btn.dart b/lib/plugin/pl_player/widgets/play_pause_btn.dart new file mode 100644 index 00000000..6cbe31f0 --- /dev/null +++ b/lib/plugin/pl_player/widgets/play_pause_btn.dart @@ -0,0 +1,89 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:media_kit/media_kit.dart'; +import 'package:pilipala/plugin/pl_player/index.dart'; + +class PlayOrPauseButton extends StatefulWidget { + final double? iconSize; + final Color? iconColor; + final PlPlayerController? controller; + + const PlayOrPauseButton({ + super.key, + this.iconSize, + this.iconColor, + this.controller, + }); + + @override + PlayOrPauseButtonState createState() => PlayOrPauseButtonState(); +} + +class PlayOrPauseButtonState extends State + with SingleTickerProviderStateMixin { + late final AnimationController animation; + + StreamSubscription? subscription; + late Player player; + bool isOpacity = false; + + @override + void initState() { + super.initState(); + player = widget.controller!.videoPlayerController!; + animation = AnimationController( + vsync: this, + value: player.state.playing ? 1 : 0, + duration: const Duration(milliseconds: 200), + ); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + subscription ??= player.stream.playing.listen((event) { + if (event) { + animation.forward().then((value) => { + isOpacity = true, + }); + } else { + animation.reverse().then((value) => {isOpacity = false}); + } + setState(() {}); + }); + } + + @override + void dispose() { + animation.dispose(); + subscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 34, + height: 34, + child: IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: player.playOrPause, + color: Colors.white, + iconSize: 20, + // iconSize: widget.iconSize ?? _theme(context).buttonBarButtonSize, + // color: widget.iconColor ?? _theme(context).buttonBarButtonColor, + icon: AnimatedIcon( + progress: animation, + icon: AnimatedIcons.play_pause, + color: Colors.white, + size: 20, + // size: widget.iconSize ?? _theme(context).buttonBarButtonSize, + // color: widget.iconColor ?? _theme(context).buttonBarButtonColor, + ), + ), + ); + } +}