diff --git a/assets/images/run-pokemon.gif b/assets/images/run-pokemon.gif new file mode 100644 index 000000000..bca20afc2 Binary files /dev/null and b/assets/images/run-pokemon.gif differ diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 68edd92aa..9f16ecf8b 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -59,6 +59,7 @@ class _VideoDetailPageState extends State // 当只有1p或多p未打开自动播放时,播放完成还原进度条,展示控制栏 plPlayerController!.seekTo(Duration.zero); plPlayerController!.onLockControl(false); + plPlayerController!.videoPlayerController!.pause(); } } }, diff --git a/lib/plugin/pl_player/index.dart b/lib/plugin/pl_player/index.dart index 997776f9f..721d50407 100644 --- a/lib/plugin/pl_player/index.dart +++ b/lib/plugin/pl_player/index.dart @@ -7,3 +7,4 @@ export './models/play_status.dart'; export './models/data_status.dart'; export './widgets/common_btn.dart'; export './models/play_speed.dart'; +export './widgets/app_bar_ani.dart'; diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 0dac67ebe..5fa5add96 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + 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'; @@ -10,6 +12,8 @@ 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 'package:screen_brightness/screen_brightness.dart'; +import 'package:volume_controller/volume_controller.dart'; import 'widgets/backward_seek.dart'; import 'widgets/bottom_control.dart'; @@ -43,6 +47,16 @@ class _PLVideoPlayerState extends State bool _hideSeekBackwardButton = false; bool _hideSeekForwardButton = false; + double _brightnessValue = 0.0; + bool _brightnessIndicator = false; + Timer? _brightnessTimer; + + double _volumeValue = 0.0; + bool _volumeIndicator = false; + Timer? _volumeTimer; + + bool _volumeInterceptEventStream = false; + void onDoubleTapSeekBackward() { setState(() { _mountSeekBackwardButton = true; @@ -61,6 +75,70 @@ class _PLVideoPlayerState extends State animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300)); videoController = widget.controller.videoController!; + + Future.microtask(() async { + try { + VolumeController().showSystemUI = false; + _volumeValue = await VolumeController().getVolume(); + VolumeController().listener((value) { + if (mounted && !_volumeInterceptEventStream) { + setState(() { + _volumeValue = value; + }); + } + }); + } catch (_) {} + }); + + Future.microtask(() async { + try { + _brightnessValue = await ScreenBrightness().current; + ScreenBrightness().onCurrentBrightnessChanged.listen((value) { + if (mounted) { + setState(() { + _brightnessValue = value; + }); + } + }); + } catch (_) {} + }); + } + + Future setVolume(double value) async { + try { + VolumeController().setVolume(value); + } catch (_) {} + setState(() { + _volumeValue = value; + _volumeIndicator = true; + _volumeInterceptEventStream = true; + }); + _volumeTimer?.cancel(); + _volumeTimer = Timer(const Duration(milliseconds: 200), () { + if (mounted) { + setState(() { + _volumeIndicator = false; + _volumeInterceptEventStream = false; + }); + } + }); + } + + Future setBrightness(double value) async { + try { + await ScreenBrightness().setScreenBrightness(value); + } catch (_) {} + setState(() { + _brightnessIndicator = true; + }); + _brightnessTimer?.cancel(); + _brightnessTimer = Timer(const Duration(milliseconds: 200), () { + if (mounted) { + setState(() { + _brightnessIndicator = false; + }); + } + }); } @override @@ -90,25 +168,6 @@ class _PLVideoPlayerState extends State 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, @@ -118,55 +177,267 @@ class _PLVideoPlayerState extends State padding: const EdgeInsets.all(24.0), ), ), - Padding( - padding: const EdgeInsets.only(top: 20, bottom: 15), - child: GestureDetector( - onTap: () { - _.controls = !_.showControls.value; - }, - // 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 { - // 双击右边区域 👈 - onDoubleTapSeekForward(); - } - }, - onLongPressStart: (detail) { - feedBack(); - double currentSpeed = _.playbackSpeed; - _.setDoubleSpeedStatus(true); - _.setPlaybackSpeed(currentSpeed * 2); - }, - onLongPressEnd: (details) { - double currentSpeed = _.playbackSpeed; - _.setDoubleSpeedStatus(false); - _.setPlaybackSpeed(currentSpeed / 2); - }, - // 水平位置 快进 - onHorizontalDragUpdate: (DragUpdateDetails details) {}, - onHorizontalDragEnd: (DragEndDetails details) {}, - // 垂直方向 音量/亮度调节 - onVerticalDragUpdate: (DragUpdateDetails details) {}, - onVerticalDragEnd: (DragEndDetails details) {}), + + /// 长按倍速 + Obx( + () => Align( + alignment: Alignment.topCenter, + child: FractionalTranslation( + translation: const Offset(0.0, 1), // 上下偏移量(负数向上偏移) + child: AnimatedOpacity( + curve: Curves.easeInOut, + opacity: _.doubleSpeedStatus.value ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color(0x88000000), + borderRadius: BorderRadius.circular(64.0), + ), + height: 34.0, + width: 86.0, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(width: 3), + Image.asset( + 'assets/images/run-pokemon.gif', + height: 20, + ), + const Text( + '倍速中', + style: TextStyle(color: Colors.white, fontSize: 12), + ), + const SizedBox(width: 4), + ], + ), + ), + ), + ), + ), ), + + /// 时间进度 + Obx( + () => Align( + alignment: Alignment.topCenter, + child: FractionalTranslation( + translation: const Offset(0.0, 1.0), // 上下偏移量(负数向上偏移) + child: AnimatedOpacity( + curve: Curves.easeInOut, + opacity: _.isSliderMoving.value ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color(0x88000000), + borderRadius: BorderRadius.circular(64.0), + ), + height: 34.0, + width: 100.0, + 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, + ), + ), + ], + ), + ), + ), + ), + ), + ), + + /// 音量🔊 控制条展示 + Align( + alignment: Alignment.center, + child: AnimatedOpacity( + curve: Curves.easeInOut, + opacity: _volumeIndicator ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color(0x88000000), + borderRadius: BorderRadius.circular(64.0), + ), + height: 34.0, + width: 70.0, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 34.0, + width: 28.0, + alignment: Alignment.centerRight, + child: Icon( + _volumeValue == 0.0 + ? Icons.volume_off + : _volumeValue < 0.5 + ? Icons.volume_down + : Icons.volume_up, + color: const Color(0xFFFFFFFF), + size: 20.0, + ), + ), + Expanded( + child: Text( + '${(_volumeValue * 100.0).round()}%', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 13.0, + color: Color(0xFFFFFFFF), + ), + ), + ), + const SizedBox(width: 6.0), + ], + ), + ), + ), + ), + + /// 亮度🌞 控制条展示 + Align( + alignment: Alignment.center, + child: AnimatedOpacity( + curve: Curves.easeInOut, + opacity: _brightnessIndicator ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color(0x88000000), + borderRadius: BorderRadius.circular(64.0), + ), + height: 34.0, + width: 70.0, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 30.0, + width: 28.0, + alignment: Alignment.centerRight, + child: Icon( + _brightnessValue < 1.0 / 3.0 + ? Icons.brightness_low + : _brightnessValue < 2.0 / 3.0 + ? Icons.brightness_medium + : Icons.brightness_high, + color: const Color(0xFFFFFFFF), + size: 18.0, + ), + ), + const SizedBox(width: 2.0), + Expanded( + child: Text( + '${(_brightnessValue * 100.0).round()}%', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 13.0, + color: Color(0xFFFFFFFF), + ), + ), + ), + const SizedBox(width: 6.0), + ], + ), + ), + ), + ), + + /// 手势 + Positioned.fill( + left: 16, + top: 25, + right: 15, + bottom: 15, + child: GestureDetector( + onTap: () { + _.controls = !_.showControls.value; + }, + 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 { + // 双击右边区域 👈 + onDoubleTapSeekForward(); + } + }, + onLongPressStart: (detail) { + feedBack(); + double currentSpeed = _.playbackSpeed; + _.setDoubleSpeedStatus(true); + _.setPlaybackSpeed(currentSpeed * 2); + }, + onLongPressEnd: (details) { + double currentSpeed = _.playbackSpeed; + _.setDoubleSpeedStatus(false); + _.setPlaybackSpeed(currentSpeed / 2); + }, + // 水平位置 快进 + onHorizontalDragUpdate: (DragUpdateDetails details) {}, + onHorizontalDragEnd: (DragEndDetails details) {}, + // 垂直方向 音量/亮度调节 + onVerticalDragUpdate: (DragUpdateDetails details) { + final totalWidth = MediaQuery.of(context).size.width; + final tapPosition = details.localPosition.dx; + final sectionWidth = totalWidth / 3; + + if (tapPosition < sectionWidth) { + // 左边区域 👈 + final delta = details.delta.dy; + final brightness = _brightnessValue - delta / 100.0; + final result = brightness.clamp(0.0, 1.0); + setBrightness(result); + } else if (tapPosition < sectionWidth * 2) { + // 全屏 + print('全屏'); + } else { + // 右边区域 👈 + final delta = details.delta.dy; + final volume = _volumeValue - delta / 100.0; + final result = volume.clamp(0.0, 1.0); + setVolume(result); + } + }, + onVerticalDragEnd: (DragEndDetails details) {}, + ), + ), + // 头部、底部控制条 if (_.controlsEnabled) Obx( @@ -245,26 +516,7 @@ class _PLVideoPlayerState extends State ); }, ), - // 长按倍速 - Obx( - () => Align( - alignment: Alignment.topCenter, - child: FractionalTranslation( - translation: const Offset(0.0, 1.5), // 上下偏移量(负数向上偏移) - child: Visibility( - visible: _.doubleSpeedStatus.value, - child: const Text( - '** 倍速中 **', - style: TextStyle( - fontSize: 13, - backgroundColor: Color(0xaa000000), - color: Colors.white, - ), - ), - ), - ), - ), - ), + // 锁 if (_.controlsEnabled) Obx( @@ -301,44 +553,8 @@ 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(