mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-24 02:56:58 +08:00
custom video aspectRatio
Closes #1040 Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -25,6 +25,7 @@ import 'package:PiliPlus/plugin/pl_player/models/fullscreen_mode.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/heart_beat_type.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/play_status.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/video_fit_type.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart';
|
||||
import 'package:PiliPlus/services/service_locator.dart';
|
||||
import 'package:PiliPlus/utils/accounts.dart';
|
||||
@@ -104,7 +105,7 @@ class PlPlayerController {
|
||||
|
||||
bool _isVertical = false;
|
||||
|
||||
final Rx<BoxFit> _videoFit = Rx(BoxFit.contain);
|
||||
final Rx<VideoFitType> _videoFit = Rx(VideoFitType.contain);
|
||||
late StreamSubscription<DataStatus> _dataListenerForVideoFit;
|
||||
late StreamSubscription<DataStatus> _dataListenerForEnterFullscreen;
|
||||
|
||||
@@ -220,7 +221,7 @@ class PlPlayerController {
|
||||
bool get autoplay => _autoPlay;
|
||||
|
||||
/// 视频比例
|
||||
Rx<BoxFit> get videoFit => _videoFit;
|
||||
Rx<VideoFitType> get videoFit => _videoFit;
|
||||
|
||||
/// 后台播放
|
||||
RxBool get continuePlayInBackground => _continuePlayInBackground;
|
||||
@@ -370,24 +371,21 @@ class PlPlayerController {
|
||||
: Colors.black.withValues(alpha: subtitleBgOpaticy),
|
||||
);
|
||||
|
||||
SubtitleViewConfiguration get subtitleViewConfiguration =>
|
||||
SubtitleViewConfiguration(
|
||||
style: subTitleStyle,
|
||||
padding: EdgeInsets.only(
|
||||
left: subtitlePaddingH.toDouble(),
|
||||
right: subtitlePaddingH.toDouble(),
|
||||
bottom: subtitlePaddingB.toDouble(),
|
||||
),
|
||||
textScaleFactor: 1,
|
||||
strokeWidth: subtitleBgOpaticy == 0 ? subtitleStrokeWidth : null,
|
||||
);
|
||||
late final Rx<SubtitleViewConfiguration> subtitleConfig = _getSubConfig.obs;
|
||||
|
||||
GlobalKey<VideoState> Function()? getPlayerKey;
|
||||
SubtitleViewConfiguration get _getSubConfig => SubtitleViewConfiguration(
|
||||
style: subTitleStyle,
|
||||
padding: EdgeInsets.only(
|
||||
left: subtitlePaddingH.toDouble(),
|
||||
right: subtitlePaddingH.toDouble(),
|
||||
bottom: subtitlePaddingB.toDouble(),
|
||||
),
|
||||
textScaleFactor: 1,
|
||||
strokeWidth: subtitleBgOpaticy == 0 ? subtitleStrokeWidth : null,
|
||||
);
|
||||
|
||||
void updateSubtitleStyle() {
|
||||
getPlayerKey?.call().currentState?.update(
|
||||
subtitleViewConfiguration: subtitleViewConfiguration,
|
||||
);
|
||||
subtitleConfig.value = _getSubConfig;
|
||||
}
|
||||
|
||||
void onUpdatePadding(EdgeInsets padding) {
|
||||
@@ -1271,42 +1269,32 @@ class PlPlayerController {
|
||||
}
|
||||
|
||||
/// Toggle Change the videofit accordingly
|
||||
void toggleVideoFit(BoxFit value) {
|
||||
void toggleVideoFit(VideoFitType value) {
|
||||
_videoFit.value = value;
|
||||
setVideoFit();
|
||||
getPlayerKey?.call().currentState?.update(fit: value);
|
||||
}
|
||||
|
||||
/// 缓存fit
|
||||
Future<void> setVideoFit() async {
|
||||
SmartDialog.showToast(
|
||||
_videoFit.value.toast,
|
||||
displayTime: const Duration(seconds: 1),
|
||||
);
|
||||
video.put(VideoBoxKey.cacheVideoFit, _videoFit.value.index);
|
||||
}
|
||||
|
||||
/// 读取fit
|
||||
int fitValue = Pref.cacheVideoFit;
|
||||
Future<void> getVideoFit() async {
|
||||
var attr = BoxFit.values[fitValue];
|
||||
var attr = VideoFitType.values[fitValue];
|
||||
// 由于none与scaleDown涉及视频原始尺寸,需要等待视频加载后再设置,否则尺寸会变为0,出现错误;
|
||||
if (attr == BoxFit.none || attr == BoxFit.scaleDown) {
|
||||
if (attr == VideoFitType.none || attr == VideoFitType.scaleDown) {
|
||||
if (buffered.value == Duration.zero) {
|
||||
attr = BoxFit.contain;
|
||||
attr = VideoFitType.contain;
|
||||
_dataListenerForVideoFit = dataStatus.status.listen((status) {
|
||||
if (status == DataStatus.loaded) {
|
||||
_dataListenerForVideoFit.cancel();
|
||||
var attr = BoxFit.values[fitValue];
|
||||
if (attr == BoxFit.none || attr == BoxFit.scaleDown) {
|
||||
var attr = VideoFitType.values[fitValue];
|
||||
if (attr == VideoFitType.none || attr == VideoFitType.scaleDown) {
|
||||
_videoFit.value = attr;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// fill不应该在竖屏视频生效
|
||||
} else if (attr == BoxFit.fill && isVertical) {
|
||||
attr = BoxFit.contain;
|
||||
} else if (attr == VideoFitType.fill && isVertical) {
|
||||
attr = VideoFitType.contain;
|
||||
}
|
||||
_videoFit.value = attr;
|
||||
}
|
||||
@@ -1676,17 +1664,3 @@ class PlPlayerController {
|
||||
late final RxList<double> dmTrend = <double>[].obs;
|
||||
late final RxBool showDmTreandChart = true.obs;
|
||||
}
|
||||
|
||||
extension BoxFitExt on BoxFit {
|
||||
String get desc => const ['拉伸', '自动', '裁剪', '等宽', '等高', '原始', '限制'][index];
|
||||
|
||||
String get toast => const [
|
||||
'拉伸至播放器尺寸,将产生变形(竖屏改为自动)',
|
||||
'缩放至播放器尺寸,保留黑边',
|
||||
'缩放至填满播放器,裁剪超出部分',
|
||||
'缩放至撑满播放器宽度',
|
||||
'缩放至撑满播放器高度',
|
||||
'不缩放,以视频原始尺寸显示',
|
||||
'仅超出时缩小至播放器尺寸',
|
||||
][index];
|
||||
}
|
||||
|
||||
22
lib/plugin/pl_player/models/video_fit_type.dart
Normal file
22
lib/plugin/pl_player/models/video_fit_type.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart' show BoxFit;
|
||||
|
||||
enum VideoFitType {
|
||||
fill('拉伸', boxFit: BoxFit.fill),
|
||||
contain('自动', boxFit: BoxFit.contain),
|
||||
cover('裁剪', boxFit: BoxFit.cover),
|
||||
fitWidth('等宽', boxFit: BoxFit.fitWidth),
|
||||
fitHeight('等高', boxFit: BoxFit.fitHeight),
|
||||
none('原始', boxFit: BoxFit.none),
|
||||
scaleDown('限制', boxFit: BoxFit.scaleDown),
|
||||
ratio_4x3('4:3', aspectRatio: 4 / 3),
|
||||
ratio_16x9('16:9', aspectRatio: 16 / 9);
|
||||
|
||||
final String desc;
|
||||
final BoxFit boxFit;
|
||||
final double? aspectRatio;
|
||||
const VideoFitType(
|
||||
this.desc, {
|
||||
this.boxFit = BoxFit.contain,
|
||||
this.aspectRatio,
|
||||
});
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import 'package:PiliPlus/plugin/pl_player/models/double_tap_type.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/duration.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/fullscreen_mode.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/gesture_type.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/video_fit_type.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/widgets/app_bar_ani.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/widgets/backward_seek.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/widgets/bottom_control.dart';
|
||||
@@ -167,7 +168,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
plPlayerController.getPlayerKey = () => key;
|
||||
_controlsListener = plPlayerController.showControls.listen((bool val) {
|
||||
final visible = val && !plPlayerController.controlsLock.value;
|
||||
visible ? animationController.forward() : animationController.reverse();
|
||||
@@ -379,6 +379,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
/// 超分辨率
|
||||
BottomControlType.superResolution => Obx(
|
||||
() => PopupMenuButton<SuperResolutionType>(
|
||||
requestFocus: false,
|
||||
initialValue: plPlayerController.superResolutionType.value,
|
||||
color: Colors.black.withValues(alpha: 0.8),
|
||||
itemBuilder: (context) {
|
||||
@@ -485,13 +486,14 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
|
||||
/// 画面比例
|
||||
BottomControlType.fit => Obx(
|
||||
() => PopupMenuButton<BoxFit>(
|
||||
() => PopupMenuButton<VideoFitType>(
|
||||
requestFocus: false,
|
||||
initialValue: plPlayerController.videoFit.value,
|
||||
color: Colors.black.withValues(alpha: 0.8),
|
||||
itemBuilder: (context) {
|
||||
return BoxFit.values
|
||||
return VideoFitType.values
|
||||
.map(
|
||||
(BoxFit boxFit) => PopupMenuItem<BoxFit>(
|
||||
(boxFit) => PopupMenuItem<VideoFitType>(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(left: 30),
|
||||
value: boxFit,
|
||||
@@ -519,6 +521,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
() => widget.videoDetailController?.subtitles.isEmpty == true
|
||||
? const SizedBox.shrink()
|
||||
: PopupMenuButton<int>(
|
||||
requestFocus: false,
|
||||
initialValue: widget
|
||||
.videoDetailController!
|
||||
.vttSubtitlesIndex
|
||||
@@ -570,6 +573,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
/// 播放速度
|
||||
BottomControlType.speed => Obx(
|
||||
() => PopupMenuButton<double>(
|
||||
requestFocus: false,
|
||||
initialValue: plPlayerController.playbackSpeed,
|
||||
color: Colors.black.withValues(alpha: 0.8),
|
||||
itemBuilder: (context) {
|
||||
@@ -670,383 +674,387 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
|
||||
late final transformationController = TransformationController();
|
||||
|
||||
late ThemeData theme;
|
||||
late double maxWidth;
|
||||
late double maxHeight;
|
||||
|
||||
void _onInteractionStart(ScaleStartDetails details) {
|
||||
if (plPlayerController.controlsLock.value) return;
|
||||
// 如果起点太靠上则屏蔽
|
||||
if (details.localFocalPoint.dy < 40) return;
|
||||
if (details.localFocalPoint.dx < 40) return;
|
||||
if (details.localFocalPoint.dx > maxWidth - 40) return;
|
||||
if (details.localFocalPoint.dy > maxHeight - 40) return;
|
||||
if (details.pointerCount == 2) {
|
||||
interacting = true;
|
||||
}
|
||||
_initialFocalPoint = details.localFocalPoint;
|
||||
// if (kDebugMode) {
|
||||
// debugPrint("_initialFocalPoint$_initialFocalPoint");
|
||||
// }
|
||||
_gestureType = null;
|
||||
}
|
||||
|
||||
void _onInteractionUpdate(ScaleUpdateDetails details) {
|
||||
showRestoreScaleBtn.value = transformationController.value.row0.x != 1.0;
|
||||
if (interacting || _initialFocalPoint == Offset.zero) return;
|
||||
Offset cumulativeDelta = details.localFocalPoint - _initialFocalPoint;
|
||||
if (details.pointerCount == 2 && cumulativeDelta.distance < 1.5) {
|
||||
interacting = true;
|
||||
_gestureType = null;
|
||||
return;
|
||||
}
|
||||
|
||||
/// 锁定时禁用
|
||||
if (plPlayerController.controlsLock.value) return;
|
||||
|
||||
if (_gestureType == null) {
|
||||
if (cumulativeDelta.distance < 1) return;
|
||||
if (cumulativeDelta.dx.abs() > 3 * cumulativeDelta.dy.abs()) {
|
||||
_gestureType = GestureType.horizontal;
|
||||
} else if (cumulativeDelta.dy.abs() > 3 * cumulativeDelta.dx.abs()) {
|
||||
if (!plPlayerController.enableSlideVolumeBrightness &&
|
||||
!plPlayerController.enableSlideFS) {
|
||||
return;
|
||||
}
|
||||
|
||||
// _gestureType = 'vertical';
|
||||
|
||||
final double tapPosition = details.localFocalPoint.dx;
|
||||
final double sectionWidth = maxWidth / 3;
|
||||
if (tapPosition < sectionWidth) {
|
||||
if (!plPlayerController.enableSlideVolumeBrightness) {
|
||||
return;
|
||||
}
|
||||
// 左边区域
|
||||
_gestureType = GestureType.left;
|
||||
} else if (tapPosition < sectionWidth * 2) {
|
||||
if (!plPlayerController.enableSlideFS) {
|
||||
return;
|
||||
}
|
||||
// 全屏
|
||||
_gestureType = GestureType.center;
|
||||
} else {
|
||||
if (!plPlayerController.enableSlideVolumeBrightness) {
|
||||
return;
|
||||
}
|
||||
// 右边区域
|
||||
_gestureType = GestureType.right;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Offset delta = details.focalPointDelta;
|
||||
|
||||
if (_gestureType == GestureType.horizontal) {
|
||||
// live模式下禁用
|
||||
if (plPlayerController.isLive) return;
|
||||
|
||||
final int curSliderPosition =
|
||||
plPlayerController.sliderPosition.value.inMilliseconds;
|
||||
final int newPos =
|
||||
(curSliderPosition +
|
||||
(plPlayerController.sliderScale * delta.dx / maxWidth)
|
||||
.round())
|
||||
.clamp(
|
||||
0,
|
||||
plPlayerController.duration.value.inMilliseconds,
|
||||
);
|
||||
final Duration result = Duration(milliseconds: newPos);
|
||||
final height = maxHeight * 0.125;
|
||||
if (details.localFocalPoint.dy <= height &&
|
||||
(details.localFocalPoint.dx >= maxWidth * 0.875 ||
|
||||
details.localFocalPoint.dx <= maxWidth * 0.125)) {
|
||||
plPlayerController.cancelSeek = true;
|
||||
plPlayerController.showPreview.value = false;
|
||||
if (plPlayerController.hasToast != true) {
|
||||
plPlayerController.hasToast = true;
|
||||
SmartDialog.showAttach(
|
||||
targetContext: context,
|
||||
alignment: Alignment.center,
|
||||
animationTime: const Duration(milliseconds: 200),
|
||||
animationType: SmartAnimationType.fade,
|
||||
displayTime: const Duration(milliseconds: 1500),
|
||||
maskColor: Colors.transparent,
|
||||
builder: (context) => Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
),
|
||||
color: theme.colorScheme.secondaryContainer,
|
||||
),
|
||||
child: Text(
|
||||
'松开手指,取消进退',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (plPlayerController.cancelSeek == true) {
|
||||
plPlayerController
|
||||
..cancelSeek = null
|
||||
..hasToast = null;
|
||||
}
|
||||
}
|
||||
plPlayerController
|
||||
..onUpdatedSliderProgress(result)
|
||||
..onChangedSliderStart();
|
||||
if (plPlayerController.showSeekPreview &&
|
||||
plPlayerController.cancelSeek != true) {
|
||||
plPlayerController.updatePreviewIndex(newPos ~/ 1000);
|
||||
}
|
||||
} else if (_gestureType == GestureType.left) {
|
||||
// 左边区域 👈
|
||||
final double level = maxHeight * 3;
|
||||
final double brightness = _brightnessValue.value - delta.dy / level;
|
||||
final double result = brightness.clamp(0.0, 1.0);
|
||||
setBrightness(result);
|
||||
} else if (_gestureType == GestureType.center) {
|
||||
// 全屏
|
||||
const double threshold = 2.5; // 滑动阈值
|
||||
double cumulativeDy = details.localFocalPoint.dy - _initialFocalPoint.dy;
|
||||
|
||||
void fullScreenTrigger(bool status) {
|
||||
plPlayerController.triggerFullScreen(status: status);
|
||||
}
|
||||
|
||||
if (cumulativeDy > threshold) {
|
||||
_gestureType = GestureType.center_down;
|
||||
if (isFullScreen ^ plPlayerController.fullScreenGestureReverse) {
|
||||
fullScreenTrigger(
|
||||
plPlayerController.fullScreenGestureReverse,
|
||||
);
|
||||
}
|
||||
// if (kDebugMode) debugPrint('center_down:$cumulativeDy');
|
||||
} else if (cumulativeDy < -threshold) {
|
||||
_gestureType = GestureType.center_up;
|
||||
if (!isFullScreen ^ plPlayerController.fullScreenGestureReverse) {
|
||||
fullScreenTrigger(
|
||||
!plPlayerController.fullScreenGestureReverse,
|
||||
);
|
||||
}
|
||||
// if (kDebugMode) debugPrint('center_up:$cumulativeDy');
|
||||
}
|
||||
} else if (_gestureType == GestureType.right) {
|
||||
// 右边区域
|
||||
final double level = maxHeight * 0.5;
|
||||
EasyThrottle.throttle(
|
||||
'setVolume',
|
||||
const Duration(milliseconds: 20),
|
||||
() {
|
||||
final double volume = _volumeValue.value - delta.dy / level;
|
||||
final double result = volume.clamp(0.0, 1.0);
|
||||
setVolume(result);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onInteractionEnd(ScaleEndDetails details) {
|
||||
if (plPlayerController.showSeekPreview) {
|
||||
plPlayerController.showPreview.value = false;
|
||||
}
|
||||
if (plPlayerController.isSliderMoving.value) {
|
||||
if (plPlayerController.cancelSeek == true) {
|
||||
plPlayerController.onUpdatedSliderProgress(
|
||||
plPlayerController.position.value,
|
||||
);
|
||||
} else {
|
||||
plPlayerController.seekTo(
|
||||
plPlayerController.sliderPosition.value,
|
||||
isSeek: false,
|
||||
);
|
||||
}
|
||||
plPlayerController.onChangedSliderEnd();
|
||||
}
|
||||
interacting = false;
|
||||
_initialFocalPoint = Offset.zero;
|
||||
_gestureType = null;
|
||||
}
|
||||
|
||||
void onVerticalDragStart(DragStartDetails details) {
|
||||
if (plPlayerController.controlsLock.value) return;
|
||||
if (details.localPosition.dy < 40) return;
|
||||
if (details.localPosition.dx < 40) return;
|
||||
if (details.localPosition.dx > maxWidth - 40) return;
|
||||
if (details.localPosition.dy > maxHeight - 40) return;
|
||||
_initialFocalPoint = details.localPosition;
|
||||
_gestureType = null;
|
||||
}
|
||||
|
||||
void onVerticalDragUpdate(DragUpdateDetails details) {
|
||||
if (plPlayerController.controlsLock.value) return;
|
||||
if (!plPlayerController.enableSlideVolumeBrightness &&
|
||||
!plPlayerController.enableSlideFS) {
|
||||
return;
|
||||
}
|
||||
final double tapPosition = details.localPosition.dx;
|
||||
final double sectionWidth = maxWidth / 3;
|
||||
late GestureType gestureType;
|
||||
if (tapPosition < sectionWidth) {
|
||||
if (!plPlayerController.enableSlideVolumeBrightness) {
|
||||
return;
|
||||
}
|
||||
// 左边区域
|
||||
gestureType = GestureType.left;
|
||||
} else if (tapPosition < sectionWidth * 2) {
|
||||
if (!plPlayerController.enableSlideFS) {
|
||||
return;
|
||||
}
|
||||
// 全屏
|
||||
gestureType = GestureType.center;
|
||||
} else {
|
||||
if (!plPlayerController.enableSlideVolumeBrightness) {
|
||||
return;
|
||||
}
|
||||
// 右边区域
|
||||
gestureType = GestureType.right;
|
||||
}
|
||||
|
||||
if (_gestureType != null && _gestureType != gestureType) {
|
||||
return;
|
||||
}
|
||||
_gestureType = gestureType;
|
||||
|
||||
if (_gestureType == GestureType.left) {
|
||||
// 左边区域 👈
|
||||
final double level = maxHeight * 3;
|
||||
final double brightness =
|
||||
_brightnessValue.value - details.delta.dy / level;
|
||||
final double result = brightness.clamp(0.0, 1.0);
|
||||
setBrightness(result);
|
||||
} else if (_gestureType == GestureType.center) {
|
||||
// 全屏
|
||||
const double threshold = 2.5; // 滑动阈值
|
||||
double cumulativeDy = details.localPosition.dy - _initialFocalPoint.dy;
|
||||
|
||||
void fullScreenTrigger(bool status) {
|
||||
plPlayerController.triggerFullScreen(status: status);
|
||||
}
|
||||
|
||||
if (cumulativeDy > threshold) {
|
||||
_gestureType = GestureType.center_down;
|
||||
if (isFullScreen ^ plPlayerController.fullScreenGestureReverse) {
|
||||
fullScreenTrigger(
|
||||
plPlayerController.fullScreenGestureReverse,
|
||||
);
|
||||
}
|
||||
// if (kDebugMode) debugPrint('center_down:$cumulativeDy');
|
||||
} else if (cumulativeDy < -threshold) {
|
||||
_gestureType = GestureType.center_up;
|
||||
if (!isFullScreen ^ plPlayerController.fullScreenGestureReverse) {
|
||||
fullScreenTrigger(
|
||||
!plPlayerController.fullScreenGestureReverse,
|
||||
);
|
||||
}
|
||||
// if (kDebugMode) debugPrint('center_up:$cumulativeDy');
|
||||
}
|
||||
} else if (_gestureType == GestureType.right) {
|
||||
// 右边区域
|
||||
final double level = maxHeight * 0.5;
|
||||
EasyThrottle.throttle(
|
||||
'setVolume',
|
||||
const Duration(milliseconds: 20),
|
||||
() {
|
||||
final double volume = _volumeValue.value - details.delta.dy / level;
|
||||
final double result = volume.clamp(0.0, 1.0);
|
||||
setVolume(result);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void onVerticalDragEnd(DragEndDetails details) {
|
||||
interacting = false;
|
||||
_initialFocalPoint = Offset.zero;
|
||||
_gestureType = null;
|
||||
}
|
||||
|
||||
void onDoubleTapDown(TapDownDetails details) {
|
||||
if (plPlayerController.controlsLock.value) {
|
||||
return;
|
||||
}
|
||||
if (plPlayerController.isLive) {
|
||||
doubleTapFuc(DoubleTapType.center);
|
||||
return;
|
||||
}
|
||||
final double tapPosition = details.localPosition.dx;
|
||||
final double sectionWidth = maxWidth / 4;
|
||||
DoubleTapType type;
|
||||
if (tapPosition < sectionWidth) {
|
||||
type = DoubleTapType.left;
|
||||
} else if (tapPosition < sectionWidth * 3) {
|
||||
type = DoubleTapType.center;
|
||||
} else {
|
||||
type = DoubleTapType.right;
|
||||
}
|
||||
doubleTapFuc(type);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
theme = Theme.of(context);
|
||||
maxWidth = widget.maxWidth;
|
||||
maxHeight = widget.maxHeight;
|
||||
final Color primary = theme.colorScheme.primary;
|
||||
const TextStyle textStyle = TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
);
|
||||
|
||||
final maxWidth = widget.maxWidth;
|
||||
final maxHeight = widget.maxHeight;
|
||||
|
||||
return Stack(
|
||||
fit: StackFit.passthrough,
|
||||
key: _playerKey,
|
||||
children: <Widget>[
|
||||
Obx(
|
||||
() => Video(
|
||||
fill: widget.fill ?? Colors.black,
|
||||
key: key,
|
||||
alignment: widget.alignment ?? Alignment.center,
|
||||
controller: videoController,
|
||||
controls: NoVideoControls,
|
||||
pauseUponEnteringBackgroundMode:
|
||||
!plPlayerController.continuePlayInBackground.value,
|
||||
resumeUponEnteringForegroundMode: true,
|
||||
// 字幕尺寸调节
|
||||
subtitleViewConfiguration:
|
||||
plPlayerController.subtitleViewConfiguration,
|
||||
fit: plPlayerController.videoFit.value,
|
||||
dmWidget: widget.danmuWidget,
|
||||
transformationController: transformationController,
|
||||
scaleEnabled: !plPlayerController.controlsLock.value,
|
||||
enableShrinkVideoSize: plPlayerController.enableShrinkVideoSize,
|
||||
onInteractionStart: (ScaleStartDetails details) {
|
||||
if (plPlayerController.controlsLock.value) return;
|
||||
// 如果起点太靠上则屏蔽
|
||||
if (details.localFocalPoint.dy < 40) return;
|
||||
if (details.localFocalPoint.dx < 40) return;
|
||||
if (details.localFocalPoint.dx > maxWidth - 40) return;
|
||||
if (details.localFocalPoint.dy > maxHeight - 40) return;
|
||||
if (details.pointerCount == 2) {
|
||||
interacting = true;
|
||||
}
|
||||
_initialFocalPoint = details.localFocalPoint;
|
||||
// if (kDebugMode) {
|
||||
// debugPrint("_initialFocalPoint$_initialFocalPoint");
|
||||
// }
|
||||
_gestureType = null;
|
||||
},
|
||||
onInteractionUpdate: (ScaleUpdateDetails details) {
|
||||
showRestoreScaleBtn.value =
|
||||
transformationController.value.row0.x != 1.0;
|
||||
if (interacting || _initialFocalPoint == Offset.zero) return;
|
||||
Offset cumulativeDelta =
|
||||
details.localFocalPoint - _initialFocalPoint;
|
||||
if (details.pointerCount == 2 && cumulativeDelta.distance < 1.5) {
|
||||
interacting = true;
|
||||
_gestureType = null;
|
||||
return;
|
||||
}
|
||||
|
||||
/// 锁定时禁用
|
||||
if (plPlayerController.controlsLock.value) return;
|
||||
|
||||
if (_gestureType == null) {
|
||||
if (cumulativeDelta.distance < 1) return;
|
||||
if (cumulativeDelta.dx.abs() > 3 * cumulativeDelta.dy.abs()) {
|
||||
_gestureType = GestureType.horizontal;
|
||||
} else if (cumulativeDelta.dy.abs() >
|
||||
3 * cumulativeDelta.dx.abs()) {
|
||||
if (!plPlayerController.enableSlideVolumeBrightness &&
|
||||
!plPlayerController.enableSlideFS) {
|
||||
return;
|
||||
}
|
||||
|
||||
// _gestureType = 'vertical';
|
||||
|
||||
final double tapPosition = details.localFocalPoint.dx;
|
||||
final double sectionWidth = maxWidth / 3;
|
||||
if (tapPosition < sectionWidth) {
|
||||
if (!plPlayerController.enableSlideVolumeBrightness) {
|
||||
return;
|
||||
}
|
||||
// 左边区域
|
||||
_gestureType = GestureType.left;
|
||||
} else if (tapPosition < sectionWidth * 2) {
|
||||
if (!plPlayerController.enableSlideFS) {
|
||||
return;
|
||||
}
|
||||
// 全屏
|
||||
_gestureType = GestureType.center;
|
||||
} else {
|
||||
if (!plPlayerController.enableSlideVolumeBrightness) {
|
||||
return;
|
||||
}
|
||||
// 右边区域
|
||||
_gestureType = GestureType.right;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Offset delta = details.focalPointDelta;
|
||||
|
||||
if (_gestureType == GestureType.horizontal) {
|
||||
// live模式下禁用
|
||||
if (plPlayerController.isLive) return;
|
||||
|
||||
final int curSliderPosition =
|
||||
plPlayerController.sliderPosition.value.inMilliseconds;
|
||||
final int newPos =
|
||||
(curSliderPosition +
|
||||
(plPlayerController.sliderScale *
|
||||
delta.dx /
|
||||
maxWidth)
|
||||
.round())
|
||||
.clamp(
|
||||
0,
|
||||
plPlayerController.duration.value.inMilliseconds,
|
||||
);
|
||||
final Duration result = Duration(milliseconds: newPos);
|
||||
final height = maxHeight * 0.125;
|
||||
if (details.localFocalPoint.dy <= height &&
|
||||
(details.localFocalPoint.dx >= maxWidth * 0.875 ||
|
||||
details.localFocalPoint.dx <= maxWidth * 0.125)) {
|
||||
plPlayerController.cancelSeek = true;
|
||||
plPlayerController.showPreview.value = false;
|
||||
if (plPlayerController.hasToast != true) {
|
||||
plPlayerController.hasToast = true;
|
||||
SmartDialog.showAttach(
|
||||
targetContext: context,
|
||||
alignment: Alignment.center,
|
||||
animationTime: const Duration(milliseconds: 200),
|
||||
animationType: SmartAnimationType.fade,
|
||||
displayTime: const Duration(milliseconds: 1500),
|
||||
maskColor: Colors.transparent,
|
||||
builder: (context) => Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
),
|
||||
color: theme.colorScheme.secondaryContainer,
|
||||
),
|
||||
child: Text(
|
||||
'松开手指,取消进退',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (plPlayerController.cancelSeek == true) {
|
||||
plPlayerController
|
||||
..cancelSeek = null
|
||||
..hasToast = null;
|
||||
}
|
||||
}
|
||||
plPlayerController
|
||||
..onUpdatedSliderProgress(result)
|
||||
..onChangedSliderStart();
|
||||
if (plPlayerController.showSeekPreview &&
|
||||
plPlayerController.cancelSeek != true) {
|
||||
plPlayerController.updatePreviewIndex(newPos ~/ 1000);
|
||||
}
|
||||
} else if (_gestureType == GestureType.left) {
|
||||
// 左边区域 👈
|
||||
final double level = maxHeight * 3;
|
||||
final double brightness =
|
||||
_brightnessValue.value - delta.dy / level;
|
||||
final double result = brightness.clamp(0.0, 1.0);
|
||||
setBrightness(result);
|
||||
} else if (_gestureType == GestureType.center) {
|
||||
// 全屏
|
||||
const double threshold = 2.5; // 滑动阈值
|
||||
double cumulativeDy =
|
||||
details.localFocalPoint.dy - _initialFocalPoint.dy;
|
||||
|
||||
void fullScreenTrigger(bool status) {
|
||||
plPlayerController.triggerFullScreen(status: status);
|
||||
}
|
||||
|
||||
if (cumulativeDy > threshold) {
|
||||
_gestureType = GestureType.center_down;
|
||||
if (isFullScreen ^
|
||||
plPlayerController.fullScreenGestureReverse) {
|
||||
fullScreenTrigger(
|
||||
plPlayerController.fullScreenGestureReverse,
|
||||
);
|
||||
}
|
||||
// if (kDebugMode) debugPrint('center_down:$cumulativeDy');
|
||||
} else if (cumulativeDy < -threshold) {
|
||||
_gestureType = GestureType.center_up;
|
||||
if (!isFullScreen ^
|
||||
plPlayerController.fullScreenGestureReverse) {
|
||||
fullScreenTrigger(
|
||||
!plPlayerController.fullScreenGestureReverse,
|
||||
);
|
||||
}
|
||||
// if (kDebugMode) debugPrint('center_up:$cumulativeDy');
|
||||
}
|
||||
} else if (_gestureType == GestureType.right) {
|
||||
// 右边区域
|
||||
final double level = maxHeight * 0.5;
|
||||
EasyThrottle.throttle(
|
||||
'setVolume',
|
||||
const Duration(milliseconds: 20),
|
||||
() {
|
||||
final double volume = _volumeValue.value - delta.dy / level;
|
||||
final double result = volume.clamp(0.0, 1.0);
|
||||
setVolume(result);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
onInteractionEnd: (ScaleEndDetails details) {
|
||||
if (plPlayerController.showSeekPreview) {
|
||||
plPlayerController.showPreview.value = false;
|
||||
}
|
||||
if (plPlayerController.isSliderMoving.value) {
|
||||
if (plPlayerController.cancelSeek == true) {
|
||||
plPlayerController.onUpdatedSliderProgress(
|
||||
plPlayerController.position.value,
|
||||
);
|
||||
} else {
|
||||
plPlayerController.seekTo(
|
||||
plPlayerController.sliderPosition.value,
|
||||
isSeek: false,
|
||||
);
|
||||
}
|
||||
plPlayerController.onChangedSliderEnd();
|
||||
}
|
||||
interacting = false;
|
||||
_initialFocalPoint = Offset.zero;
|
||||
_gestureType = null;
|
||||
},
|
||||
flipX: plPlayerController.flipX.value,
|
||||
flipY: plPlayerController.flipY.value,
|
||||
onVerticalDragStart: (details) {
|
||||
if (plPlayerController.controlsLock.value) return;
|
||||
if (details.localPosition.dy < 40) return;
|
||||
if (details.localPosition.dx < 40) return;
|
||||
if (details.localPosition.dx > maxWidth - 40) return;
|
||||
if (details.localPosition.dy > maxHeight - 40) return;
|
||||
_initialFocalPoint = details.localPosition;
|
||||
_gestureType = null;
|
||||
},
|
||||
onVerticalDragUpdate: (details) {
|
||||
if (plPlayerController.controlsLock.value) return;
|
||||
if (!plPlayerController.enableSlideVolumeBrightness &&
|
||||
!plPlayerController.enableSlideFS) {
|
||||
return;
|
||||
}
|
||||
final double tapPosition = details.localPosition.dx;
|
||||
final double sectionWidth = maxWidth / 3;
|
||||
late GestureType gestureType;
|
||||
if (tapPosition < sectionWidth) {
|
||||
if (!plPlayerController.enableSlideVolumeBrightness) {
|
||||
return;
|
||||
}
|
||||
// 左边区域
|
||||
gestureType = GestureType.left;
|
||||
} else if (tapPosition < sectionWidth * 2) {
|
||||
if (!plPlayerController.enableSlideFS) {
|
||||
return;
|
||||
}
|
||||
// 全屏
|
||||
gestureType = GestureType.center;
|
||||
} else {
|
||||
if (!plPlayerController.enableSlideVolumeBrightness) {
|
||||
return;
|
||||
}
|
||||
// 右边区域
|
||||
gestureType = GestureType.right;
|
||||
}
|
||||
|
||||
if (_gestureType != null && _gestureType != gestureType) {
|
||||
return;
|
||||
}
|
||||
_gestureType = gestureType;
|
||||
|
||||
if (_gestureType == GestureType.left) {
|
||||
// 左边区域 👈
|
||||
final double level = maxHeight * 3;
|
||||
final double brightness =
|
||||
_brightnessValue.value - details.delta.dy / level;
|
||||
final double result = brightness.clamp(0.0, 1.0);
|
||||
setBrightness(result);
|
||||
} else if (_gestureType == GestureType.center) {
|
||||
// 全屏
|
||||
const double threshold = 2.5; // 滑动阈值
|
||||
double cumulativeDy =
|
||||
details.localPosition.dy - _initialFocalPoint.dy;
|
||||
|
||||
void fullScreenTrigger(bool status) {
|
||||
plPlayerController.triggerFullScreen(status: status);
|
||||
}
|
||||
|
||||
if (cumulativeDy > threshold) {
|
||||
_gestureType = GestureType.center_down;
|
||||
if (isFullScreen ^
|
||||
plPlayerController.fullScreenGestureReverse) {
|
||||
fullScreenTrigger(
|
||||
plPlayerController.fullScreenGestureReverse,
|
||||
);
|
||||
}
|
||||
// if (kDebugMode) debugPrint('center_down:$cumulativeDy');
|
||||
} else if (cumulativeDy < -threshold) {
|
||||
_gestureType = GestureType.center_up;
|
||||
if (!isFullScreen ^
|
||||
plPlayerController.fullScreenGestureReverse) {
|
||||
fullScreenTrigger(
|
||||
!plPlayerController.fullScreenGestureReverse,
|
||||
);
|
||||
}
|
||||
// if (kDebugMode) debugPrint('center_up:$cumulativeDy');
|
||||
}
|
||||
} else if (_gestureType == GestureType.right) {
|
||||
// 右边区域
|
||||
final double level = maxHeight * 0.5;
|
||||
EasyThrottle.throttle(
|
||||
'setVolume',
|
||||
const Duration(milliseconds: 20),
|
||||
() {
|
||||
final double volume =
|
||||
_volumeValue.value - details.delta.dy / level;
|
||||
final double result = volume.clamp(0.0, 1.0);
|
||||
setVolume(result);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
onVerticalDragEnd: (details) {
|
||||
interacting = false;
|
||||
_initialFocalPoint = Offset.zero;
|
||||
_gestureType = null;
|
||||
},
|
||||
onTap: () {
|
||||
plPlayerController.controls =
|
||||
!plPlayerController.showControls.value;
|
||||
},
|
||||
onDoubleTapDown: (TapDownDetails details) {
|
||||
if (plPlayerController.controlsLock.value) {
|
||||
return;
|
||||
}
|
||||
if (plPlayerController.isLive) {
|
||||
doubleTapFuc(DoubleTapType.center);
|
||||
return;
|
||||
}
|
||||
final double tapPosition = details.localPosition.dx;
|
||||
final double sectionWidth = maxWidth / 4;
|
||||
DoubleTapType type;
|
||||
if (tapPosition < sectionWidth) {
|
||||
type = DoubleTapType.left;
|
||||
} else if (tapPosition < sectionWidth * 3) {
|
||||
type = DoubleTapType.center;
|
||||
} else {
|
||||
type = DoubleTapType.right;
|
||||
}
|
||||
doubleTapFuc(type);
|
||||
},
|
||||
onLongPressStart: (LongPressStartDetails detail) {
|
||||
plPlayerController.setLongPressStatus(true);
|
||||
},
|
||||
onLongPressEnd: (LongPressEndDetails details) {
|
||||
plPlayerController.setLongPressStatus(false);
|
||||
},
|
||||
enableDragSubtitle: plPlayerController.enableDragSubtitle,
|
||||
onUpdatePadding: plPlayerController.onUpdatePadding,
|
||||
),
|
||||
() {
|
||||
final videoFit = plPlayerController.videoFit.value;
|
||||
return Video(
|
||||
fill: widget.fill ?? Colors.black,
|
||||
key: key,
|
||||
alignment: widget.alignment ?? Alignment.center,
|
||||
controller: videoController,
|
||||
controls: NoVideoControls,
|
||||
pauseUponEnteringBackgroundMode:
|
||||
!plPlayerController.continuePlayInBackground.value,
|
||||
resumeUponEnteringForegroundMode: true,
|
||||
// 字幕尺寸调节
|
||||
subtitleViewConfiguration:
|
||||
plPlayerController.subtitleConfig.value,
|
||||
fit: videoFit.boxFit,
|
||||
aspectRatio: videoFit.aspectRatio,
|
||||
dmWidget: widget.danmuWidget,
|
||||
transformationController: transformationController,
|
||||
scaleEnabled: !plPlayerController.controlsLock.value,
|
||||
enableShrinkVideoSize: plPlayerController.enableShrinkVideoSize,
|
||||
onInteractionStart: _onInteractionStart,
|
||||
onInteractionUpdate: _onInteractionUpdate,
|
||||
onInteractionEnd: _onInteractionEnd,
|
||||
flipX: plPlayerController.flipX.value,
|
||||
flipY: plPlayerController.flipY.value,
|
||||
onVerticalDragStart: onVerticalDragStart,
|
||||
onVerticalDragUpdate: onVerticalDragUpdate,
|
||||
onVerticalDragEnd: onVerticalDragEnd,
|
||||
onTap: () => plPlayerController.controls =
|
||||
!plPlayerController.showControls.value,
|
||||
onDoubleTapDown: onDoubleTapDown,
|
||||
onLongPressStart: (_) =>
|
||||
plPlayerController.setLongPressStatus(true),
|
||||
onLongPressEnd: (_) =>
|
||||
plPlayerController.setLongPressStatus(false),
|
||||
enableDragSubtitle: plPlayerController.enableDragSubtitle,
|
||||
onUpdatePadding: plPlayerController.onUpdatePadding,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// /// 弹幕面板
|
||||
|
||||
Reference in New Issue
Block a user