fix: obx不能返回nil,无障碍适配

This commit is contained in:
orz12
2024-02-29 20:59:33 +08:00
parent 1d6b3049d9
commit 646424d7c2

View File

@@ -1,7 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:flutter_volume_controller/flutter_volume_controller.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -17,6 +17,8 @@ import 'package:PiliPalaX/utils/feed_back.dart';
import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/storage.dart';
import 'package:screen_brightness/screen_brightness.dart'; import 'package:screen_brightness/screen_brightness.dart';
import '../../common/widgets/audio_video_progress_bar.dart';
import '../../utils/utils.dart';
import 'models/bottom_progress_behavior.dart'; import 'models/bottom_progress_behavior.dart';
import 'widgets/app_bar_ani.dart'; import 'widgets/app_bar_ani.dart';
import 'widgets/backward_seek.dart'; import 'widgets/backward_seek.dart';
@@ -79,6 +81,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
DateTime? lastFullScreenToggleTime; DateTime? lastFullScreenToggleTime;
// 记录上一次音量调整值作平均,避免音量调整抖动 // 记录上一次音量调整值作平均,避免音量调整抖动
double lastVolume = -1.0; double lastVolume = -1.0;
// 是否在调整固定进度条
RxBool draggingFixedProgressBar = false.obs;
// 阅读器限制
Timer? _accessibilityDebounce;
double _lastAnnouncedValue = -1;
void onDoubleTapSeekBackward() { void onDoubleTapSeekBackward() {
_ctr.onDoubleTapSeekBackward(); _ctr.onDoubleTapSeekBackward();
@@ -127,8 +134,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
defaultValue: BtmProgresBehavior.values.first.code); defaultValue: BtmProgresBehavior.values.first.code);
enableQuickDouble = enableQuickDouble =
setting.get(SettingBoxKey.enableQuickDouble, defaultValue: true); setting.get(SettingBoxKey.enableQuickDouble, defaultValue: true);
fullScreenGestureReverse = setting.get(SettingBoxKey.fullScreenGestureReverse, fullScreenGestureReverse = setting
defaultValue: false); .get(SettingBoxKey.fullScreenGestureReverse, defaultValue: false);
enableBackgroundPlay = enableBackgroundPlay =
setting.get(SettingBoxKey.enableBackgroundPlay, defaultValue: false); setting.get(SettingBoxKey.enableBackgroundPlay, defaultValue: false);
Future.microtask(() async { Future.microtask(() async {
@@ -281,10 +288,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
children: [ children: [
Obx(() { Obx(() {
return Text( return Text(
_.sliderTempPosition.value.inMinutes >= 60 Utils.timeFormat(
? printDurationWithHours( _.sliderTempPosition.value.inSeconds),
_.sliderTempPosition.value)
: printDuration(_.sliderTempPosition.value),
style: textStyle, style: textStyle,
); );
}), }),
@@ -439,123 +444,131 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
top: 25, top: 25,
right: 15, right: 15,
bottom: 15, bottom: 15,
child: GestureDetector( child: Semantics(
onTap: () { label: '双击开关播放控件,左右滑动调整进度',
_.controls = !_.showControls.value; child: GestureDetector(
}, onTap: () {
onDoubleTapDown: (TapDownDetails details) { _.controls = !_.showControls.value;
// live模式下禁用 锁定时🔒禁用 },
if (_.videoType.value == 'live' || _.controlsLock.value) { onDoubleTapDown: (TapDownDetails details) {
return; // live模式下禁用 锁定时🔒禁用
} if (_.videoType.value == 'live' || _.controlsLock.value) {
RenderBox renderBox = _playerKey.currentContext!.findRenderObject() as RenderBox; return;
final double totalWidth = renderBox.size.width; }
final double tapPosition = details.localPosition.dx; RenderBox renderBox =
final double sectionWidth = totalWidth / 3; _playerKey.currentContext!.findRenderObject() as RenderBox;
String type = 'left'; final double totalWidth = renderBox.size.width;
if (tapPosition < sectionWidth) { final double tapPosition = details.localPosition.dx;
type = 'left'; final double sectionWidth = totalWidth / 3;
} else if (tapPosition < sectionWidth * 2) { String type = 'left';
type = 'center'; if (tapPosition < sectionWidth) {
} else { type = 'left';
type = 'right'; } else if (tapPosition < sectionWidth * 2) {
} type = 'center';
doubleTapFuc(type); } else {
}, type = 'right';
onLongPressStart: (LongPressStartDetails detail) { }
feedBack(); doubleTapFuc(type);
_.setDoubleSpeedStatus(true); },
}, onLongPressStart: (LongPressStartDetails detail) {
onLongPressEnd: (LongPressEndDetails details) { feedBack();
_.setDoubleSpeedStatus(false); _.setDoubleSpeedStatus(true);
}, },
onLongPressEnd: (LongPressEndDetails details) {
_.setDoubleSpeedStatus(false);
},
/// 水平位置 快进 live模式下禁用 /// 水平位置 快进 live模式下禁用
onHorizontalDragUpdate: (DragUpdateDetails details) { onHorizontalDragUpdate: (DragUpdateDetails details) {
// live模式下禁用 锁定时🔒禁用 // live模式下禁用 锁定时🔒禁用
if (_.videoType.value == 'live' || _.controlsLock.value) { if (_.videoType.value == 'live' || _.controlsLock.value) {
return; return;
} }
// final double tapPosition = details.localPosition.dx; // final double tapPosition = details.localPosition.dx;
final int curSliderPosition = final int curSliderPosition =
_.sliderPosition.value.inMilliseconds; _.sliderPosition.value.inMilliseconds;
RenderBox renderBox = _playerKey.currentContext!.findRenderObject() as RenderBox; RenderBox renderBox =
final double scale = 90000 / renderBox.size.width; _playerKey.currentContext!.findRenderObject() as RenderBox;
final Duration pos = Duration( final double scale = 90000 / renderBox.size.width;
milliseconds: final Duration pos = Duration(
curSliderPosition + (details.delta.dx * scale).round()); milliseconds:
final Duration result = curSliderPosition + (details.delta.dx * scale).round());
pos.clamp(Duration.zero, _.duration.value); final Duration result =
_.onUpdatedSliderProgress(result); pos.clamp(Duration.zero, _.duration.value);
_.onChangedSliderStart(); _.onUpdatedSliderProgress(result);
// _initTapPositoin = tapPosition; _.onChangedSliderStart();
}, // _initTapPositoin = tapPosition;
onHorizontalDragEnd: (DragEndDetails details) { },
if (_.videoType.value == 'live' || _.controlsLock.value) { onHorizontalDragEnd: (DragEndDetails details) {
return; if (_.videoType.value == 'live' || _.controlsLock.value) {
} return;
_.onChangedSliderEnd(); }
_.seekTo(_.sliderPosition.value, type: 'slider'); _.onChangedSliderEnd();
}, _.seekTo(_.sliderPosition.value, type: 'slider');
// 垂直方向 音量/亮度调节 },
onVerticalDragUpdate: (DragUpdateDetails details) async { // 垂直方向 音量/亮度调节
RenderBox renderBox = _playerKey.currentContext!.findRenderObject() as RenderBox; onVerticalDragUpdate: (DragUpdateDetails details) async {
final double totalWidth = renderBox.size.width; RenderBox renderBox =
final double tapPosition = details.localPosition.dx; _playerKey.currentContext!.findRenderObject() as RenderBox;
final double sectionWidth = totalWidth / 3;
final double delta = details.delta.dy;
/// 锁定时禁用 /// 锁定时禁用
if (_.controlsLock.value) { if (_.controlsLock.value) {
return; return;
}
if (lastFullScreenToggleTime != null &&
DateTime.now().difference(lastFullScreenToggleTime!) <
const Duration(milliseconds: 500)) {
return;
}
if (tapPosition < sectionWidth) {
// 左边区域 👈
final double level = renderBox.size.height * 3;
final double brightness =
_ctr.brightnessValue.value - delta / level;
final double result = brightness.clamp(0.0, 1.0);
setBrightness(result);
} else if (tapPosition < sectionWidth * 2) {
// 全屏
final double dy = details.delta.dy;
const double threshold = 7.0; // 滑动阈值
void fullScreenTrigger(bool status) async {
lastFullScreenToggleTime = DateTime.now();
await widget.controller.triggerFullScreen(status: status);
} }
if (dy > _distance && dy > threshold) { final double totalWidth = renderBox.size.width;
// 下滑退出全屏/进入全屏 final double tapPosition = details.localPosition.dx;
if (_.isFullScreen.value ^ fullScreenGestureReverse) { final double sectionWidth = totalWidth / 3;
fullScreenTrigger(fullScreenGestureReverse); final double delta = details.delta.dy;
if (lastFullScreenToggleTime != null &&
DateTime.now().difference(lastFullScreenToggleTime!) <
const Duration(milliseconds: 500)) {
return;
}
if (tapPosition < sectionWidth) {
// 左边区域 👈
final double level = renderBox.size.height * 3;
final double brightness =
_ctr.brightnessValue.value - delta / level;
final double result = brightness.clamp(0.0, 1.0);
setBrightness(result);
} else if (tapPosition < sectionWidth * 2) {
// 全屏
final double dy = details.delta.dy;
const double threshold = 7.0; // 滑动阈值
void fullScreenTrigger(bool status) async {
lastFullScreenToggleTime = DateTime.now();
await widget.controller.triggerFullScreen(status: status);
} }
_distance = 0.0;
} else if (dy < _distance && dy < -threshold) { if (dy > _distance && dy > threshold) {
// 上划进入全屏/退出全屏 // 下滑退出全屏/进入全屏
if (!_.isFullScreen.value ^ fullScreenGestureReverse) { if (_.isFullScreen.value ^ fullScreenGestureReverse) {
fullScreenTrigger(!fullScreenGestureReverse); fullScreenTrigger(fullScreenGestureReverse);
}
_distance = 0.0;
} else if (dy < _distance && dy < -threshold) {
// 上划进入全屏/退出全屏
if (!_.isFullScreen.value ^ fullScreenGestureReverse) {
fullScreenTrigger(!fullScreenGestureReverse);
}
_distance = 0.0;
} }
_distance = 0.0; _distance = dy;
} else {
// 右边区域 👈
final double level = renderBox.size.height * 0.5;
if (lastVolume < 0) {
lastVolume = _ctr.volumeValue.value;
}
final double volume =
(lastVolume + _ctr.volumeValue.value - delta / level) / 2;
final double result = volume.clamp(0.0, 1.0);
lastVolume = result;
setVolume(result);
} }
_distance = dy; },
} else { onVerticalDragEnd: (DragEndDetails details) {},
// 右边区域 👈 ),
final double level = renderBox.size.height * 0.5;
if(lastVolume < 0) {
lastVolume = _ctr.volumeValue.value;
}
final double volume = (lastVolume + _ctr.volumeValue.value - delta / level)/2;
final double result = volume.clamp(0.0, 1.0);
lastVolume = result;
setVolume(result);
}
},
onVerticalDragEnd: (DragEndDetails details) {},
), ),
), ),
@@ -605,65 +618,84 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
} }
if (defaultBtmProgressBehavior == if (defaultBtmProgressBehavior ==
BtmProgresBehavior.alwaysHide.code) { BtmProgresBehavior.alwaysHide.code) {
return nil; return Container();
} }
if (defaultBtmProgressBehavior == if (defaultBtmProgressBehavior ==
BtmProgresBehavior.onlyShowFullScreen.code && BtmProgresBehavior.onlyShowFullScreen.code &&
!_.isFullScreen.value) { !_.isFullScreen.value) {
return nil; return Container();
} else if (defaultBtmProgressBehavior == } else if (defaultBtmProgressBehavior ==
BtmProgresBehavior.onlyHideFullScreen.code && BtmProgresBehavior.onlyHideFullScreen.code &&
_.isFullScreen.value) { _.isFullScreen.value) {
return nil; return Container();
} }
if (_.videoType.value == 'live') { if (_.videoType.value == 'live') {
return const SizedBox(); return Container();
} }
if (value > max || max <= 0) { if (value > max || max <= 0) {
return nil; return Container();
} }
return Positioned( return Positioned(
bottom: -1.5, bottom: -1,
left: 0, left: 0,
right: 0, right: 0,
child: ProgressBar( child: Semantics(
progress: Duration(seconds: value), // label: '${(value / max * 100).round()}%',
buffered: Duration(seconds: buffer), value: '${(value / max * 100).round()}%',
total: Duration(seconds: max), // enabled: false,
progressBarColor: colorTheme, child: ProgressBar(
baseBarColor: Colors.white.withOpacity(0.2), progress: Duration(seconds: value),
bufferedBarColor: buffered: Duration(seconds: buffer),
Theme.of(context).colorScheme.primary.withOpacity(0.4), total: Duration(seconds: max),
timeLabelLocation: TimeLabelLocation.none, progressBarColor: colorTheme,
thumbColor: colorTheme, baseBarColor: Colors.white.withOpacity(0.2),
barHeight: 3, bufferedBarColor:
thumbRadius: 0.0, Theme.of(context).colorScheme.primary.withOpacity(0.4),
// onDragStart: (duration) { timeLabelLocation: TimeLabelLocation.none,
// _.onChangedSliderStart(); thumbColor: colorTheme,
// }, barHeight: 3.5,
// onDragEnd: () { thumbRadius: draggingFixedProgressBar.value ? 7 : 4,
// _.onChangedSliderEnd(); onDragStart: (duration) {
// }, draggingFixedProgressBar.value = true;
// onDragUpdate: (details) { feedBack();
// print(details); _.onChangedSliderStart();
// }, },
// onSeek: (duration) { onDragUpdate: (duration) {
// feedBack(); double newProgress = duration.timeStamp.inSeconds / max;
// _.onChangedSlider(duration.inSeconds.toDouble()); if ((newProgress - _lastAnnouncedValue).abs() > 0.02) {
// _.seekTo(duration); _accessibilityDebounce?.cancel();
// }, _accessibilityDebounce =
), Timer(const Duration(milliseconds: 200), () {
// SlideTransition( SemanticsService.announce(
// position: Tween<Offset>( "${(newProgress * 100).round()}%",
// begin: Offset.zero, TextDirection.ltr);
// end: const Offset(0, -1), _lastAnnouncedValue = newProgress;
// ).animate(CurvedAnimation( });
// parent: animationController, }
// curve: Curves.easeInOut, _.onUpdatedSliderProgress(duration.timeStamp);
// )), },
// child: ), onSeek: (duration) {
); draggingFixedProgressBar.value = false;
_.onChangedSliderEnd();
_.onChangedSlider(duration.inSeconds.toDouble());
_.seekTo(Duration(seconds: duration.inSeconds),
type: 'slider');
SemanticsService.announce(
"${(duration.inSeconds / max * 100).round()}%",
TextDirection.ltr);
},
),
// SlideTransition(
// position: Tween<Offset>(
// begin: Offset.zero,
// end: const Offset(0, -1),
// ).animate(CurvedAnimation(
// parent: animationController,
// curve: Curves.easeInOut,
// )),
// child: ),
));
}, },
), ),
@@ -678,6 +710,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
child: Visibility( child: Visibility(
visible: _.showControls.value, visible: _.showControls.value,
child: ComBtn( child: ComBtn(
tooltip: _.controlsLock.value ? '解锁' : '锁定',
icon: Icon( icon: Icon(
_.controlsLock.value _.controlsLock.value
? FontAwesomeIcons.lock ? FontAwesomeIcons.lock