diff --git a/lib/pages/search/controller.dart b/lib/pages/search/controller.dart index e4c2dc9ae..9de71970a 100644 --- a/lib/pages/search/controller.dart +++ b/lib/pages/search/controller.dart @@ -59,10 +59,8 @@ class SSearchController extends GetxController { controller.text = Get.parameters['text']!; } - if (recordSearchHistory.value) { - historyList = - List.from(GStorage.historyWord.get('cacheList') ?? []).obs; - } + historyList = + List.from(GStorage.historyWord.get('cacheList') ?? []).obs; if (searchSuggestion) { _ctr = StreamController(); diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index 920d69e9d..efaf57827 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -80,7 +80,7 @@ class _SearchPageState extends State { if (_searchController.searchSuggestion) _searchSuggest(), if (context.orientation == Orientation.portrait) ...[ if (_searchController.enableHotKey) hotSearch(theme), - if (_searchController.recordSearchHistory.value) _history(theme), + _history(theme), if (_searchController.enableSearchRcmd) hotSearch(theme, false) ] else Row( @@ -97,8 +97,7 @@ class _SearchPageState extends State { ], ), ), - if (_searchController.recordSearchHistory.value) - Expanded(child: _history(theme)), + Expanded(child: _history(theme)), ], ), ], @@ -207,28 +206,6 @@ class _SearchPageState extends State { ), ), ), - // deprecated - // SizedBox( - // height: 34, - // child: TextButton( - // onPressed: () => Get.toNamed('/dynTopicRcmd'), - // child: Row( - // children: [ - // Text( - // '话题', - // strutStyle: - // const StrutStyle(leading: 0, height: 1), - // style: style, - // ), - // Icon( - // size: 18, - // Icons.keyboard_arrow_right, - // color: outline, - // ), - // ], - // ), - // ), - // ), ], ) : text, @@ -272,23 +249,26 @@ class _SearchPageState extends State { } Widget _history(ThemeData theme) { - final secondary = theme.colorScheme.secondary; return Obx( - () => Padding( - padding: EdgeInsets.fromLTRB( - 10, - context.orientation == Orientation.landscape - ? 25 - : _searchController.enableHotKey - ? 0 - : 6, - 6, - 25, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (_searchController.historyList.isNotEmpty) + () { + if (_searchController.historyList.isEmpty) { + return const SizedBox.shrink(); + } + final secondary = theme.colorScheme.secondary; + return Padding( + padding: EdgeInsets.fromLTRB( + 10, + context.orientation == Orientation.landscape + ? 25 + : _searchController.enableHotKey + ? 0 + : 6, + 6, + 25, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Padding( padding: const EdgeInsets.fromLTRB(6, 0, 6, 6), child: Row( @@ -301,28 +281,30 @@ class _SearchPageState extends State { ), const SizedBox(width: 12), Obx( - () => SizedBox( - width: 34, - height: 34, - child: IconButton( - iconSize: 22, - tooltip: _searchController.recordSearchHistory.value - ? '记录搜索' - : '无痕搜索', - icon: _searchController.recordSearchHistory.value - ? historyIcon(theme) - : historyIcon(theme).disable(), - style: IconButton.styleFrom(padding: EdgeInsets.zero), - onPressed: () { - _searchController.recordSearchHistory.value = - !_searchController.recordSearchHistory.value; - GStorage.setting.put( - SettingBoxKey.recordSearchHistory, - _searchController.recordSearchHistory.value, - ); - }, - ), - ), + () { + bool enable = + _searchController.recordSearchHistory.value; + return SizedBox( + width: 34, + height: 34, + child: IconButton( + iconSize: 22, + tooltip: enable ? '记录搜索' : '无痕搜索', + icon: enable + ? historyIcon(theme) + : historyIcon(theme).disable(), + style: + IconButton.styleFrom(padding: EdgeInsets.zero), + onPressed: () { + enable = !enable; + _searchController.recordSearchHistory.value = + enable; + GStorage.setting.put( + SettingBoxKey.recordSearchHistory, enable); + }, + ), + ); + }, ), const Spacer(), SizedBox( @@ -351,8 +333,7 @@ class _SearchPageState extends State { ], ), ), - Obx( - () => Wrap( + Wrap( spacing: 8, runSpacing: 8, direction: Axis.horizontal, @@ -367,10 +348,10 @@ class _SearchPageState extends State { ) .toList(), ), - ), - ], - ), - ), + ], + ), + ); + }, ); } diff --git a/lib/plugin/pl_player/models/play_speed.dart b/lib/plugin/pl_player/models/play_speed.dart index 20cc2d2f3..4a4dfc5a2 100644 --- a/lib/plugin/pl_player/models/play_speed.dart +++ b/lib/plugin/pl_player/models/play_speed.dart @@ -13,6 +13,4 @@ enum PlaySpeed { final double value; const PlaySpeed(this.value); - - String get desc => value == 1.0 ? '正常' : value.toString(); } diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 2ad0df72c..5c85e443d 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -693,456 +693,506 @@ class _PLVideoPlayerState extends State color: Colors.white, fontSize: 12, ); - return Stack( - fit: StackFit.passthrough, - key: _playerKey, - children: [ - 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 > context.width - 40) return; - if (details.localFocalPoint.dy > context.height - 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; - } + return LayoutBuilder( + builder: (context, constraints) { + final maxWidth = constraints.maxWidth; + final maxHeight = constraints.maxHeight; + return Stack( + fit: StackFit.passthrough, + key: _playerKey, + children: [ + 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 (plPlayerController.controlsLock.value) return; - RenderBox renderBox = - _playerKey.currentContext!.findRenderObject() as RenderBox; + if (_gestureType == null) { + if (cumulativeDelta.distance < 1) return; + if (cumulativeDelta.dx.abs() > + 3 * cumulativeDelta.dy.abs()) { + _gestureType = 'horizontal'; + } else if (cumulativeDelta.dy.abs() > + 3 * cumulativeDelta.dx.abs()) { + if (!plPlayerController.enableSlideVolumeBrightness && + !plPlayerController.enableSlideFS) { + return; + } - if (_gestureType == null) { - if (cumulativeDelta.distance < 1) return; - if (cumulativeDelta.dx.abs() > 3 * cumulativeDelta.dy.abs()) { - _gestureType = 'horizontal'; - } else if (cumulativeDelta.dy.abs() > - 3 * cumulativeDelta.dx.abs()) { + // _gestureType = 'vertical'; + + final double tapPosition = details.localFocalPoint.dx; + final double sectionWidth = maxWidth / 3; + if (tapPosition < sectionWidth) { + if (!plPlayerController.enableSlideVolumeBrightness) { + return; + } + // 左边区域 + _gestureType = 'left'; + } else if (tapPosition < sectionWidth * 2) { + if (!plPlayerController.enableSlideFS) { + return; + } + // 全屏 + _gestureType = 'center'; + } else { + if (!plPlayerController.enableSlideVolumeBrightness) { + return; + } + // 右边区域 + _gestureType = 'right'; + } + } else { + return; + } + } + + Offset delta = details.focalPointDelta; + + if (_gestureType == 'horizontal') { + // live模式下禁用 + if (plPlayerController.videoType.value == 'live') return; + + final int curSliderPosition = + plPlayerController.sliderPosition.value.inMilliseconds; + final Duration pos = Duration( + milliseconds: curSliderPosition + + (plPlayerController.sliderScale * + delta.dx / + maxWidth) + .round()); // TODO + final Duration result = pos.clamp( + Duration.zero, plPlayerController.duration.value); + 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) { + try { + plPlayerController.previewDx.value = + result.inMilliseconds / + plPlayerController + .durationSeconds.value.inMilliseconds * + maxWidth; + if (!plPlayerController.showPreview.value) { + plPlayerController.showPreview.value = true; + } + } catch (_) {} + } + } else if (_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 == 'center') { + // 全屏 + const double threshold = 2.5; // 滑动阈值 + double cumulativeDy = + details.localFocalPoint.dy - _initialFocalPoint.dy; + + void fullScreenTrigger(bool status) { + plPlayerController.triggerFullScreen( + status: status, duration: 800); + } + + if (cumulativeDy > threshold) { + _gestureType = 'center_down'; + if (isFullScreen ^ + plPlayerController.fullScreenGestureReverse) { + fullScreenTrigger( + plPlayerController.fullScreenGestureReverse); + } + // if (kDebugMode) debugPrint('center_down:$cumulativeDy'); + } else if (cumulativeDy < -threshold) { + _gestureType = 'center_up'; + if (!isFullScreen ^ + plPlayerController.fullScreenGestureReverse) { + fullScreenTrigger( + !plPlayerController.fullScreenGestureReverse); + } + // if (kDebugMode) debugPrint('center_up:$cumulativeDy'); + } + } else if (_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, + type: 'slider', + ); + } + 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 > context.width - 40) return; + if (details.localPosition.dy > context.height - 40) return; + _initialFocalPoint = details.localPosition; + _gestureType = null; + }, + onVerticalDragUpdate: (details) { + if (plPlayerController.controlsLock.value) return; if (!plPlayerController.enableSlideVolumeBrightness && !plPlayerController.enableSlideFS) { return; } - - // _gestureType = 'vertical'; - - final double totalWidth = renderBox.size.width; - final double tapPosition = details.localFocalPoint.dx; - final double sectionWidth = totalWidth / 3; + final double tapPosition = details.localPosition.dx; + final double sectionWidth = maxWidth / 3; + late String gestureType; if (tapPosition < sectionWidth) { if (!plPlayerController.enableSlideVolumeBrightness) { return; } // 左边区域 - _gestureType = 'left'; + gestureType = 'left'; } else if (tapPosition < sectionWidth * 2) { if (!plPlayerController.enableSlideFS) { return; } // 全屏 - _gestureType = 'center'; + gestureType = 'center'; } else { if (!plPlayerController.enableSlideVolumeBrightness) { return; } // 右边区域 - _gestureType = 'right'; + gestureType = 'right'; } - } else { - return; - } - } - Offset delta = details.focalPointDelta; + if (_gestureType != null && _gestureType != gestureType) { + return; + } + _gestureType = gestureType; - if (_gestureType == 'horizontal') { - // live模式下禁用 - if (plPlayerController.videoType.value == 'live') return; + if (_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 == 'center') { + // 全屏 + const double threshold = 2.5; // 滑动阈值 + double cumulativeDy = + details.localPosition.dy - _initialFocalPoint.dy; - final int curSliderPosition = - plPlayerController.sliderPosition.value.inMilliseconds; - final double width = renderBox.size.width; - final Duration pos = Duration( - milliseconds: curSliderPosition + - (plPlayerController.sliderScale * delta.dx / width) - .round()); // TODO - final Duration result = - pos.clamp(Duration.zero, plPlayerController.duration.value); - final height = renderBox.size.height * 0.125; - if (details.localFocalPoint.dy <= height && - (details.localFocalPoint.dx >= width * 0.875 || - details.localFocalPoint.dx <= width * 0.125)) { - plPlayerController.cancelSeek = true; - plPlayerController.showPreview.value = false; - if (plPlayerController.hasToast != true) { - plPlayerController.hasToast = true; - SmartDialog.showAttach( - targetContext: context, + void fullScreenTrigger(bool status) { + plPlayerController.triggerFullScreen(status: status); + } + + if (cumulativeDy > threshold) { + _gestureType = 'center_down'; + if (isFullScreen ^ + plPlayerController.fullScreenGestureReverse) { + fullScreenTrigger( + plPlayerController.fullScreenGestureReverse); + } + // if (kDebugMode) debugPrint('center_down:$cumulativeDy'); + } else if (cumulativeDy < -threshold) { + _gestureType = 'center_up'; + if (!isFullScreen ^ + plPlayerController.fullScreenGestureReverse) { + fullScreenTrigger( + !plPlayerController.fullScreenGestureReverse); + } + // if (kDebugMode) debugPrint('center_up:$cumulativeDy'); + } + } else if (_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.videoType.value == 'live') { + doubleTapFuc('center'); + return; + } + final double tapPosition = details.localPosition.dx; + final double sectionWidth = maxWidth / 4; + String type = 'left'; + if (tapPosition < sectionWidth) { + type = 'left'; + } else if (tapPosition < sectionWidth * 3) { + type = 'center'; + } else { + type = 'right'; + } + doubleTapFuc(type); + }, + onLongPressStart: (LongPressStartDetails detail) { + plPlayerController.setLongPressStatus(true); + }, + onLongPressEnd: (LongPressEndDetails details) { + plPlayerController.setLongPressStatus(false); + }, + enableDragSubtitle: plPlayerController.enableDragSubtitle, + onUpdatePadding: plPlayerController.onUpdatePadding, + ), + ), + + // /// 弹幕面板 + // if (widget.danmuWidget != null) + // Positioned.fill(top: 4, child: widget.danmuWidget!), + + /// 长按倍速 toast + Obx( + () => Align( + alignment: Alignment.topCenter, + child: FractionalTranslation( + translation: const Offset(0.0, 0.3), // 上下偏移量(负数向上偏移) + child: AnimatedOpacity( + curve: Curves.easeInOut, + opacity: + plPlayerController.longPressStatus.value ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), + child: Container( 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, + decoration: const BoxDecoration( + color: Color(0x88000000), + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + height: 32.0, + width: 70.0, + child: Center( + child: Obx( + () => Text( + '${plPlayerController.enableAutoLongPressSpeed ? (plPlayerController.longPressStatus.value ? plPlayerController.lastPlaybackSpeed : plPlayerController.playbackSpeed) * 2 : plPlayerController.longPressSpeed}倍速中', + style: const TextStyle( + color: Colors.white, fontSize: 13), ), ), ), - ); - } - } else { - if (plPlayerController.cancelSeek == true) { - plPlayerController - ..cancelSeek = null - ..hasToast = null; - } - } - plPlayerController - ..onUpdatedSliderProgress(result) - ..onChangedSliderStart(); - if (plPlayerController.showSeekPreview && - plPlayerController.cancelSeek != true) { - try { - plPlayerController.previewDx.value = result.inMilliseconds / - plPlayerController - .durationSeconds.value.inMilliseconds * - renderBox.size.width; - if (!plPlayerController.showPreview.value) { - plPlayerController.showPreview.value = true; - } - } catch (_) {} - } - } else if (_gestureType == 'left') { - // 左边区域 👈 - final double level = renderBox.size.height * 3; - final double brightness = - _brightnessValue.value - delta.dy / level; - final double result = brightness.clamp(0.0, 1.0); - setBrightness(result); - } else if (_gestureType == 'center') { - // 全屏 - const double threshold = 2.5; // 滑动阈值 - double cumulativeDy = - details.localFocalPoint.dy - _initialFocalPoint.dy; - - void fullScreenTrigger(bool status) { - plPlayerController.triggerFullScreen( - status: status, duration: 800); - } - - if (cumulativeDy > threshold) { - _gestureType = 'center_down'; - if (isFullScreen ^ - plPlayerController.fullScreenGestureReverse) { - fullScreenTrigger( - plPlayerController.fullScreenGestureReverse); - } - // if (kDebugMode) debugPrint('center_down:$cumulativeDy'); - } else if (cumulativeDy < -threshold) { - _gestureType = 'center_up'; - if (!isFullScreen ^ - plPlayerController.fullScreenGestureReverse) { - fullScreenTrigger( - !plPlayerController.fullScreenGestureReverse); - } - // if (kDebugMode) debugPrint('center_up:$cumulativeDy'); - } - } else if (_gestureType == 'right') { - // 右边区域 - final double level = renderBox.size.height * 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, - type: 'slider', - ); - } - 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 > context.width - 40) return; - if (details.localPosition.dy > context.height - 40) return; - _initialFocalPoint = details.localPosition; - _gestureType = null; - }, - onVerticalDragUpdate: (details) { - if (plPlayerController.controlsLock.value) return; - if (!plPlayerController.enableSlideVolumeBrightness && - !plPlayerController.enableSlideFS) { - return; - } - RenderBox renderBox = - _playerKey.currentContext!.findRenderObject() as RenderBox; - final double totalWidth = renderBox.size.width; - final double tapPosition = details.localPosition.dx; - final double sectionWidth = totalWidth / 3; - late String gestureType; - if (tapPosition < sectionWidth) { - if (!plPlayerController.enableSlideVolumeBrightness) { - return; - } - // 左边区域 - gestureType = 'left'; - } else if (tapPosition < sectionWidth * 2) { - if (!plPlayerController.enableSlideFS) { - return; - } - // 全屏 - gestureType = 'center'; - } else { - if (!plPlayerController.enableSlideVolumeBrightness) { - return; - } - // 右边区域 - gestureType = 'right'; - } - - if (_gestureType != null && _gestureType != gestureType) { - return; - } - _gestureType = gestureType; - - if (_gestureType == 'left') { - // 左边区域 👈 - final double level = renderBox.size.height * 3; - final double brightness = - _brightnessValue.value - details.delta.dy / level; - final double result = brightness.clamp(0.0, 1.0); - setBrightness(result); - } else if (_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 = 'center_down'; - if (isFullScreen ^ - plPlayerController.fullScreenGestureReverse) { - fullScreenTrigger( - plPlayerController.fullScreenGestureReverse); - } - // if (kDebugMode) debugPrint('center_down:$cumulativeDy'); - } else if (cumulativeDy < -threshold) { - _gestureType = 'center_up'; - if (!isFullScreen ^ - plPlayerController.fullScreenGestureReverse) { - fullScreenTrigger( - !plPlayerController.fullScreenGestureReverse); - } - // if (kDebugMode) debugPrint('center_up:$cumulativeDy'); - } - } else if (_gestureType == 'right') { - // 右边区域 - final double level = renderBox.size.height * 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.videoType.value == 'live') { - doubleTapFuc('center'); - return; - } - RenderBox renderBox = - _playerKey.currentContext!.findRenderObject() as RenderBox; - final double totalWidth = renderBox.size.width; - final double tapPosition = details.localPosition.dx; - final double sectionWidth = totalWidth / 4; - String type = 'left'; - if (tapPosition < sectionWidth) { - type = 'left'; - } else if (tapPosition < sectionWidth * 3) { - type = 'center'; - } else { - type = 'right'; - } - doubleTapFuc(type); - }, - onLongPressStart: (LongPressStartDetails detail) { - plPlayerController.setLongPressStatus(true); - }, - onLongPressEnd: (LongPressEndDetails details) { - plPlayerController.setLongPressStatus(false); - }, - enableDragSubtitle: plPlayerController.enableDragSubtitle, - onUpdatePadding: plPlayerController.onUpdatePadding, - ), - ), - - // /// 弹幕面板 - // if (widget.danmuWidget != null) - // Positioned.fill(top: 4, child: widget.danmuWidget!), - - /// 长按倍速 toast - Obx( - () => Align( - alignment: Alignment.topCenter, - child: FractionalTranslation( - translation: const Offset(0.0, 0.3), // 上下偏移量(负数向上偏移) - child: AnimatedOpacity( - curve: Curves.easeInOut, - opacity: plPlayerController.longPressStatus.value ? 1.0 : 0.0, - duration: const Duration(milliseconds: 150), - child: Container( - alignment: Alignment.center, - decoration: const BoxDecoration( - color: Color(0x88000000), - borderRadius: BorderRadius.all(Radius.circular(16)), - ), - height: 32.0, - width: 70.0, - child: Center( - child: Obx( - () => Text( - '${plPlayerController.enableAutoLongPressSpeed ? (plPlayerController.longPressStatus.value ? plPlayerController.lastPlaybackSpeed : plPlayerController.playbackSpeed) * 2 : plPlayerController.longPressSpeed}倍速中', - style: - const TextStyle(color: Colors.white, fontSize: 13), - ), ), ), ), ), ), - ), - ), - /// 时间进度 toast - IgnorePointer( - ignoring: true, - child: Align( - alignment: Alignment.topCenter, - child: FractionalTranslation( - translation: const Offset(0.0, 1.0), // 上下偏移量(负数向上偏移) - child: Obx( - () => AnimatedOpacity( - curve: Curves.easeInOut, - opacity: plPlayerController.isSliderMoving.value ? 1.0 : 0.0, - duration: const Duration(milliseconds: 150), - child: IntrinsicWidth( + /// 时间进度 toast + IgnorePointer( + ignoring: true, + child: Align( + alignment: Alignment.topCenter, + child: FractionalTranslation( + translation: const Offset(0.0, 1.0), // 上下偏移量(负数向上偏移) + child: Obx( + () => AnimatedOpacity( + curve: Curves.easeInOut, + opacity: + plPlayerController.isSliderMoving.value ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), + child: IntrinsicWidth( + child: Container( + alignment: Alignment.center, + decoration: const BoxDecoration( + color: Color(0x88000000), + borderRadius: BorderRadius.all(Radius.circular(64)), + ), + height: 34.0, + padding: const EdgeInsets.only(left: 10, right: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Obx(() { + return Text( + DurationUtil.formatDuration(plPlayerController + .sliderTempPosition.value.inSeconds), + style: textStyle, + ); + }), + const SizedBox(width: 2), + const Text('/', style: textStyle), + const SizedBox(width: 2), + Obx( + () => Text( + plPlayerController.durationSeconds.value + .inMinutes >= + 60 + ? printDurationWithHours( + plPlayerController + .durationSeconds.value) + : printDuration(plPlayerController + .durationSeconds.value), + style: textStyle, + ), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ), + + /// 音量🔊 控制条展示 + IgnorePointer( + ignoring: true, + child: Align( + alignment: Alignment.center, + child: Obx( + () => AnimatedOpacity( + curve: Curves.easeInOut, + opacity: _volumeIndicator.value ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), child: Container( - alignment: Alignment.center, + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 5), decoration: const BoxDecoration( color: Color(0x88000000), borderRadius: BorderRadius.all(Radius.circular(64)), ), - height: 34.0, - padding: const EdgeInsets.only(left: 10, right: 10), child: Row( + mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, - children: [ - Obx(() { - return Text( - DurationUtil.formatDuration(plPlayerController - .sliderTempPosition.value.inSeconds), - style: textStyle, - ); - }), - const SizedBox(width: 2), - const Text('/', style: textStyle), - const SizedBox(width: 2), - Obx( - () => Text( - plPlayerController - .durationSeconds.value.inMinutes >= - 60 - ? printDurationWithHours( - plPlayerController.durationSeconds.value) - : printDuration( - plPlayerController.durationSeconds.value), - style: textStyle, + children: [ + Icon( + _volumeValue.value == 0.0 + ? Icons.volume_off + : _volumeValue.value < 0.5 + ? Icons.volume_down + : Icons.volume_up, + color: Colors.white, + size: 20.0, + ), + const SizedBox(width: 2.0), + Text( + '${(_volumeValue.value * 100.0).round()}%', + style: const TextStyle( + fontSize: 13.0, + color: Colors.white, ), ), ], @@ -1152,582 +1202,552 @@ class _PLVideoPlayerState extends State ), ), ), - ), - ), - /// 音量🔊 控制条展示 - IgnorePointer( - ignoring: true, - child: Align( - alignment: Alignment.center, - child: Obx( - () => AnimatedOpacity( - curve: Curves.easeInOut, - opacity: _volumeIndicator.value ? 1.0 : 0.0, - duration: const Duration(milliseconds: 150), - child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 5), - decoration: const BoxDecoration( - color: Color(0x88000000), - borderRadius: BorderRadius.all(Radius.circular(64)), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - _volumeValue.value == 0.0 - ? Icons.volume_off - : _volumeValue.value < 0.5 - ? Icons.volume_down - : Icons.volume_up, - color: Colors.white, - size: 20.0, - ), - const SizedBox(width: 2.0), - Text( - '${(_volumeValue.value * 100.0).round()}%', - style: const TextStyle( - fontSize: 13.0, - color: Colors.white, - ), - ), - ], - ), - ), - ), - ), - ), - ), - - /// 亮度🌞 控制条展示 - IgnorePointer( - ignoring: true, - child: Align( - alignment: Alignment.center, - child: Obx( - () => AnimatedOpacity( - curve: Curves.easeInOut, - opacity: _brightnessIndicator.value ? 1.0 : 0.0, - duration: const Duration(milliseconds: 150), - child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 5), - decoration: const BoxDecoration( - color: Color(0x88000000), - borderRadius: BorderRadius.all(Radius.circular(64)), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - _brightnessValue.value < 1.0 / 3.0 - ? Icons.brightness_low - : _brightnessValue.value < 2.0 / 3.0 - ? Icons.brightness_medium - : Icons.brightness_high, - color: Colors.white, - size: 18.0, - ), - const SizedBox(width: 2.0), - Text( - '${(_brightnessValue.value * 100.0).round()}%', - style: const TextStyle( - fontSize: 13.0, - color: Colors.white, - ), - ), - ], - ), - ), - ), - ), - ), - ), - - // 头部、底部控制条 - Obx( - () => Positioned.fill( - child: ClipRect( - child: Column( - children: [ - AppBarAni( - controller: animationController, - visible: !plPlayerController.controlsLock.value && - plPlayerController.showControls.value, - position: 'top', - child: widget.headerControl, - ), - const Spacer(), - AppBarAni( - controller: animationController, - visible: !plPlayerController.controlsLock.value && - plPlayerController.showControls.value, - position: 'bottom', - child: widget.bottomControl ?? - BottomControl( - controller: plPlayerController, - buildBottomControl: buildBottomControl, - ), - ), - ], - ), - ), - ), - ), - - // if (BuildConfig.isDebug) - // Positioned( - // right: 25, - // top: 125, - // child: FilledButton.tonal( - // onPressed: () { - // transformationController.value = Matrix4.identity() - // ..translate(0.5, 0.5) - // ..scale(1.2) - // ..translate(-0.5, -0.5); - - // showRestoreScaleBtn.value = true; - // }, - // child: const Text('scale'), - // ), - // ), - - Obx( - () => showRestoreScaleBtn.value && - plPlayerController.showControls.value - ? Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: const EdgeInsets.only(bottom: 95), - child: FilledButton.tonal( - style: FilledButton.styleFrom( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - backgroundColor: theme.colorScheme.secondaryContainer - .withValues(alpha: 0.8), - visualDensity: VisualDensity.compact, - padding: const EdgeInsets.all(15), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(6)), - ), - ), - onPressed: () async { - showRestoreScaleBtn.value = false; - final animController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 255), - ); - final anim = Matrix4Tween( - begin: transformationController.value, - end: Matrix4.identity(), - ).animate( - CurveTween(curve: Curves.easeOut) - .animate(animController), - ); - void listener() { - transformationController.value = anim.value; - } - - animController.addListener(listener); - await animController.forward(from: 0); - animController - ..removeListener(listener) - ..dispose(); - }, - child: const Text('还原屏幕'), - ), - ), - ) - : const SizedBox.shrink(), - ), - - /// 进度条 live模式下禁用 - Obx( - () { - final int value = plPlayerController.sliderPositionSeconds.value; - final int max = plPlayerController.durationSeconds.value.inSeconds; - final int buffer = plPlayerController.bufferedSeconds.value; - if (plPlayerController.showControls.value) { - return const SizedBox.shrink(); - } - - switch (plPlayerController.defaultBtmProgressBehavior) { - case BtmProgressBehavior.alwaysShow: - break; - case BtmProgressBehavior.alwaysHide: - return const SizedBox.shrink(); - case BtmProgressBehavior.onlyShowFullScreen: - if (!isFullScreen) { - return const SizedBox.shrink(); - } - case BtmProgressBehavior.onlyHideFullScreen: - if (isFullScreen) { - return const SizedBox.shrink(); - } - } - - if (plPlayerController.videoType.value == 'live') { - return const SizedBox.shrink(); - } - - if (value > max || max <= 0) { - return const SizedBox.shrink(); - } - - return Positioned( - bottom: -2.2, - left: 0, - right: 0, - child: Semantics( - // label: '${(value / max * 100).round()}%', - value: '${(value / max * 100).round()}%', - // enabled: false, - child: Stack( - clipBehavior: Clip.none, - alignment: Alignment.bottomCenter, - children: [ - if (plPlayerController.dmTrend.isNotEmpty && - plPlayerController.showDmTreandChart.value) - buildDmChart(theme, plPlayerController), - if (plPlayerController.viewPointList.isNotEmpty && - plPlayerController.showVP.value) - buildViewPointWidget(plPlayerController, 4.25), - IgnorePointer( - child: ProgressBar( - progress: Duration(seconds: value), - buffered: Duration(seconds: buffer), - total: Duration(seconds: max), - progressBarColor: primary, - baseBarColor: Colors.white.withValues(alpha: 0.2), - bufferedBarColor: primary.withValues(alpha: 0.4), - timeLabelLocation: TimeLabelLocation.none, - thumbColor: primary, - barHeight: 3.5, - thumbRadius: draggingFixedProgressBar.value ? 7 : 2.5, - // onDragStart: (duration) { - // feedBack(); - // plPlayerController.onChangedSliderStart(); - // }, - // onDragUpdate: (duration) { - // plPlayerController - // .onUpdatedSliderProgress(duration.timeStamp); - // if (plPlayerController.showSeekPreview) { - // if (plPlayerController.showPreview.value.not) { - // plPlayerController.showPreview.value = true; - // } - // plPlayerController.previewDx.value = - // duration.localPosition.dx; - // } - // }, - // onSeek: (duration) { - // if (plPlayerController.showSeekPreview) { - // plPlayerController.showPreview.value = false; - // } - // plPlayerController.onChangedSliderEnd(); - // plPlayerController - // .onChangedSlider(duration.inSeconds.toDouble()); - // plPlayerController.seekTo( - // Duration(seconds: duration.inSeconds), - // type: 'slider'); - // }, - ), - ), - if (plPlayerController.segmentList.isNotEmpty) - Positioned( - left: 0, - right: 0, - bottom: 0.75, - child: IgnorePointer( - child: CustomPaint( - size: const Size(double.infinity, 3.5), - painter: SegmentProgressBar( - segmentColors: plPlayerController.segmentList, - ), - ), - ), - ), - if (plPlayerController.viewPointList.isNotEmpty && - plPlayerController.showVP.value) - Positioned( - left: 0, - right: 0, - bottom: 0.75, - child: IgnorePointer( - child: CustomPaint( - size: const Size(double.infinity, 3.5), - painter: SegmentProgressBar( - segmentColors: plPlayerController.viewPointList, - ), - ), - ), - ), - if (plPlayerController.showSeekPreview) - Positioned( - left: 0, - right: 0, - bottom: 12, - child: buildSeekPreviewWidget(plPlayerController), - ), - ], - ), - // SlideTransition( - // position: Tween( - // begin: Offset.zero, - // end: const Offset(0, -1), - // ).animate(CurvedAnimation( - // parent: animationController, - // curve: Curves.easeInOut, - // )), - // child: ), - )); - }, - ), - - // 锁 - SafeArea( - child: Obx( - () => Visibility( - visible: - plPlayerController.videoType.value != 'live' && isFullScreen, + /// 亮度🌞 控制条展示 + IgnorePointer( + ignoring: true, child: Align( - alignment: Alignment.centerLeft, - child: FractionalTranslation( - translation: const Offset(1, -0.4), - child: Visibility( - visible: plPlayerController.showControls.value && - (isFullScreen || plPlayerController.controlsLock.value), - child: DecoratedBox( - decoration: const BoxDecoration( - color: Color(0x45000000), - borderRadius: BorderRadius.all(Radius.circular(8)), - ), - child: ComBtn( - icon: Icon( - plPlayerController.controlsLock.value - ? FontAwesomeIcons.lock - : FontAwesomeIcons.lockOpen, - semanticLabel: plPlayerController.controlsLock.value - ? '解锁' - : '锁定', - size: 15, - color: Colors.white, - ), - onTap: () => plPlayerController.onLockControl( - !plPlayerController.controlsLock.value), - ), - ), - ), - ), - ), - ), - ), - ), - - // 截图 - SafeArea( - child: Obx( - () => Align( - alignment: Alignment.centerRight, - child: FractionalTranslation( - translation: const Offset(-1, -0.4), - child: Visibility( - visible: - plPlayerController.showControls.value && isFullScreen, - child: DecoratedBox( - decoration: const BoxDecoration( - color: Color(0x45000000), - borderRadius: BorderRadius.all(Radius.circular(8)), - ), - child: ComBtn( - icon: const Icon( - Icons.photo_camera, - semanticLabel: '截图', - size: 20, - color: Colors.white, - ), - onTap: () { - SmartDialog.showToast('截图中'); - plPlayerController.videoPlayerController - ?.screenshot(format: 'image/png') - .then((value) { - if (value != null && context.mounted) { - SmartDialog.showToast('点击弹窗保存截图'); - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - // title: const Text('点击保存'), - titlePadding: EdgeInsets.zero, - contentPadding: const EdgeInsets.all(8), - insetPadding: - EdgeInsets.only(left: context.width / 2), - //移除圆角 - shape: const RoundedRectangleBorder(), - content: GestureDetector( - onTap: () async { - String name = DateTime.now().toString(); - final SaveResult result = - await SaverGallery.saveImage( - value, - fileName: name, - androidRelativePath: - "Pictures/Screenshots", - skipIfExists: false, - ); - - if (result.isSuccess) { - Get.back(); - SmartDialog.showToast( - '$name.png已保存到相册/截图'); - } else { - await SmartDialog.showToast( - '保存失败,${result.errorMessage}'); - } - }, - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: context.width / 3, - maxHeight: context.height / 3, - ), - child: Image.memory(value), - ), - ), - ); - }, - ); - } else { - SmartDialog.showToast('截图失败'); - } - }); - }, - ), - ), - ), - ), - ), - ), - ), - - Obx(() { - if (plPlayerController.dataStatus.loading || - plPlayerController.isBuffering.value) { - return Center( - child: GestureDetector( - onTap: plPlayerController.refreshPlayer, + alignment: Alignment.center, + child: Obx( + () => AnimatedOpacity( + curve: Curves.easeInOut, + opacity: _brightnessIndicator.value ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), child: Container( - padding: const EdgeInsets.all(30), + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 5), decoration: const BoxDecoration( - shape: BoxShape.circle, - gradient: RadialGradient( - colors: [Colors.black26, Colors.transparent], - ), + color: Color(0x88000000), + borderRadius: BorderRadius.all(Radius.circular(64)), ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - Image.asset( - 'assets/images/loading.gif', - height: 25, - semanticLabel: "加载中", - ), - if (plPlayerController.isBuffering.value) - Obx(() { - if (plPlayerController.buffered.value == - Duration.zero) { - return const Text('加载中...', - style: TextStyle( - color: Colors.white, fontSize: 12)); - } - String bufferStr = - plPlayerController.buffered.toString(); - return Text( - bufferStr.substring(0, bufferStr.length - 3), - style: const TextStyle( - color: Colors.white, fontSize: 12), - ); - }), - ]), - ))); - } else { - return const SizedBox.shrink(); - } - }), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + _brightnessValue.value < 1.0 / 3.0 + ? Icons.brightness_low + : _brightnessValue.value < 2.0 / 3.0 + ? Icons.brightness_medium + : Icons.brightness_high, + color: Colors.white, + size: 18.0, + ), + const SizedBox(width: 2.0), + Text( + '${(_brightnessValue.value * 100.0).round()}%', + style: const TextStyle( + fontSize: 13.0, + color: Colors.white, + ), + ), + ], + ), + ), + ), + ), + ), + ), - /// 点击 快进/快退 - Obx( - () => _mountSeekBackwardButton.value || _mountSeekForwardButton.value - ? Positioned.fill( - child: Row( + // 头部、底部控制条 + Obx( + () => Positioned.fill( + child: ClipRect( + child: Column( children: [ - if (_mountSeekBackwardButton.value) - Expanded( - child: TweenAnimationBuilder( - tween: Tween(begin: 0.0, end: 1.0), - duration: const Duration(milliseconds: 500), - builder: (context, value, child) => Opacity( - opacity: value, - child: child, + AppBarAni( + controller: animationController, + visible: !plPlayerController.controlsLock.value && + plPlayerController.showControls.value, + position: 'top', + child: widget.headerControl, + ), + const Spacer(), + AppBarAni( + controller: animationController, + visible: !plPlayerController.controlsLock.value && + plPlayerController.showControls.value, + position: 'bottom', + child: widget.bottomControl ?? + BottomControl( + controller: plPlayerController, + buildBottomControl: buildBottomControl, ), - child: BackwardSeekIndicator( - duration: - plPlayerController.fastForBackwardDuration, - onSubmitted: (Duration value) { - _mountSeekBackwardButton.value = false; - final Player player = widget - .plPlayerController.videoPlayerController!; - Duration result = player.state.position - value; - result = result.clamp( - Duration.zero, - player.state.duration, - ); - plPlayerController - ..seekTo( - result, - type: 'slider', - ) - ..play(); - }, - ), - ), - ), - const Spacer(flex: 2), - if (_mountSeekForwardButton.value) - Expanded( - child: TweenAnimationBuilder( - tween: Tween(begin: 0.0, end: 1.0), - duration: const Duration(milliseconds: 500), - builder: (context, value, child) => Opacity( - opacity: value, - child: child, - ), - child: ForwardSeekIndicator( - duration: - plPlayerController.fastForBackwardDuration, - onSubmitted: (Duration value) { - _mountSeekForwardButton.value = false; - final Player player = widget - .plPlayerController.videoPlayerController!; - Duration result = player.state.position + value; - result = result.clamp( - Duration.zero, - player.state.duration, - ); - plPlayerController - ..seekTo( - result, - type: 'slider', - ) - ..play(); - }, - ), - ), - ), + ), ], ), - ) - : const SizedBox.shrink(), - ), - ], + ), + ), + ), + + // if (BuildConfig.isDebug) + // Positioned( + // right: 25, + // top: 125, + // child: FilledButton.tonal( + // onPressed: () { + // transformationController.value = Matrix4.identity() + // ..translate(0.5, 0.5) + // ..scale(1.2) + // ..translate(-0.5, -0.5); + + // showRestoreScaleBtn.value = true; + // }, + // child: const Text('scale'), + // ), + // ), + + Obx( + () => showRestoreScaleBtn.value && + plPlayerController.showControls.value + ? Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.only(bottom: 95), + child: FilledButton.tonal( + style: FilledButton.styleFrom( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + backgroundColor: theme + .colorScheme.secondaryContainer + .withValues(alpha: 0.8), + visualDensity: VisualDensity.compact, + padding: const EdgeInsets.all(15), + shape: const RoundedRectangleBorder( + borderRadius: + BorderRadius.all(Radius.circular(6)), + ), + ), + onPressed: () async { + showRestoreScaleBtn.value = false; + final animController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 255), + ); + final anim = Matrix4Tween( + begin: transformationController.value, + end: Matrix4.identity(), + ).animate( + CurveTween(curve: Curves.easeOut) + .animate(animController), + ); + void listener() { + transformationController.value = anim.value; + } + + animController.addListener(listener); + await animController.forward(from: 0); + animController + ..removeListener(listener) + ..dispose(); + }, + child: const Text('还原屏幕'), + ), + ), + ) + : const SizedBox.shrink(), + ), + + /// 进度条 live模式下禁用 + Obx( + () { + final int value = + plPlayerController.sliderPositionSeconds.value; + final int max = + plPlayerController.durationSeconds.value.inSeconds; + final int buffer = plPlayerController.bufferedSeconds.value; + if (plPlayerController.showControls.value) { + return const SizedBox.shrink(); + } + + switch (plPlayerController.defaultBtmProgressBehavior) { + case BtmProgressBehavior.alwaysShow: + break; + case BtmProgressBehavior.alwaysHide: + return const SizedBox.shrink(); + case BtmProgressBehavior.onlyShowFullScreen: + if (!isFullScreen) { + return const SizedBox.shrink(); + } + case BtmProgressBehavior.onlyHideFullScreen: + if (isFullScreen) { + return const SizedBox.shrink(); + } + } + + if (plPlayerController.videoType.value == 'live') { + return const SizedBox.shrink(); + } + + if (value > max || max <= 0) { + return const SizedBox.shrink(); + } + + return Positioned( + bottom: -2.2, + left: 0, + right: 0, + child: Semantics( + // label: '${(value / max * 100).round()}%', + value: '${(value / max * 100).round()}%', + // enabled: false, + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.bottomCenter, + children: [ + if (plPlayerController.dmTrend.isNotEmpty && + plPlayerController.showDmTreandChart.value) + buildDmChart(theme, plPlayerController), + if (plPlayerController.viewPointList.isNotEmpty && + plPlayerController.showVP.value) + buildViewPointWidget(plPlayerController, 4.25), + IgnorePointer( + child: ProgressBar( + progress: Duration(seconds: value), + buffered: Duration(seconds: buffer), + total: Duration(seconds: max), + progressBarColor: primary, + baseBarColor: Colors.white.withValues(alpha: 0.2), + bufferedBarColor: primary.withValues(alpha: 0.4), + timeLabelLocation: TimeLabelLocation.none, + thumbColor: primary, + barHeight: 3.5, + thumbRadius: + draggingFixedProgressBar.value ? 7 : 2.5, + // onDragStart: (duration) { + // feedBack(); + // plPlayerController.onChangedSliderStart(); + // }, + // onDragUpdate: (duration) { + // plPlayerController + // .onUpdatedSliderProgress(duration.timeStamp); + // if (plPlayerController.showSeekPreview) { + // if (plPlayerController.showPreview.value.not) { + // plPlayerController.showPreview.value = true; + // } + // plPlayerController.previewDx.value = + // duration.localPosition.dx; + // } + // }, + // onSeek: (duration) { + // if (plPlayerController.showSeekPreview) { + // plPlayerController.showPreview.value = false; + // } + // plPlayerController.onChangedSliderEnd(); + // plPlayerController + // .onChangedSlider(duration.inSeconds.toDouble()); + // plPlayerController.seekTo( + // Duration(seconds: duration.inSeconds), + // type: 'slider'); + // }, + ), + ), + if (plPlayerController.segmentList.isNotEmpty) + Positioned( + left: 0, + right: 0, + bottom: 0.75, + child: IgnorePointer( + child: CustomPaint( + size: const Size(double.infinity, 3.5), + painter: SegmentProgressBar( + segmentColors: + plPlayerController.segmentList, + ), + ), + ), + ), + if (plPlayerController.viewPointList.isNotEmpty && + plPlayerController.showVP.value) + Positioned( + left: 0, + right: 0, + bottom: 0.75, + child: IgnorePointer( + child: CustomPaint( + size: const Size(double.infinity, 3.5), + painter: SegmentProgressBar( + segmentColors: + plPlayerController.viewPointList, + ), + ), + ), + ), + if (plPlayerController.showSeekPreview) + Positioned( + left: 0, + right: 0, + bottom: 12, + child: buildSeekPreviewWidget(plPlayerController), + ), + ], + ), + // SlideTransition( + // position: Tween( + // begin: Offset.zero, + // end: const Offset(0, -1), + // ).animate(CurvedAnimation( + // parent: animationController, + // curve: Curves.easeInOut, + // )), + // child: ), + )); + }, + ), + + // 锁 + SafeArea( + child: Obx( + () => Visibility( + visible: plPlayerController.videoType.value != 'live' && + isFullScreen, + child: Align( + alignment: Alignment.centerLeft, + child: FractionalTranslation( + translation: const Offset(1, -0.4), + child: Visibility( + visible: plPlayerController.showControls.value && + (isFullScreen || + plPlayerController.controlsLock.value), + child: DecoratedBox( + decoration: const BoxDecoration( + color: Color(0x45000000), + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + child: ComBtn( + icon: Icon( + plPlayerController.controlsLock.value + ? FontAwesomeIcons.lock + : FontAwesomeIcons.lockOpen, + semanticLabel: + plPlayerController.controlsLock.value + ? '解锁' + : '锁定', + size: 15, + color: Colors.white, + ), + onTap: () => plPlayerController.onLockControl( + !plPlayerController.controlsLock.value), + ), + ), + ), + ), + ), + ), + ), + ), + + // 截图 + SafeArea( + child: Obx( + () => Align( + alignment: Alignment.centerRight, + child: FractionalTranslation( + translation: const Offset(-1, -0.4), + child: Visibility( + visible: + plPlayerController.showControls.value && isFullScreen, + child: DecoratedBox( + decoration: const BoxDecoration( + color: Color(0x45000000), + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + child: ComBtn( + icon: const Icon( + Icons.photo_camera, + semanticLabel: '截图', + size: 20, + color: Colors.white, + ), + onTap: () { + SmartDialog.showToast('截图中'); + plPlayerController.videoPlayerController + ?.screenshot(format: 'image/png') + .then((value) { + if (value != null && context.mounted) { + SmartDialog.showToast('点击弹窗保存截图'); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + // title: const Text('点击保存'), + titlePadding: EdgeInsets.zero, + contentPadding: const EdgeInsets.all(8), + insetPadding: EdgeInsets.only( + left: context.width / 2), + //移除圆角 + shape: const RoundedRectangleBorder(), + content: GestureDetector( + onTap: () async { + String name = + DateTime.now().toString(); + final SaveResult result = + await SaverGallery.saveImage( + value, + fileName: name, + androidRelativePath: + "Pictures/Screenshots", + skipIfExists: false, + ); + + if (result.isSuccess) { + Get.back(); + SmartDialog.showToast( + '$name.png已保存到相册/截图'); + } else { + await SmartDialog.showToast( + '保存失败,${result.errorMessage}'); + } + }, + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: maxWidth / 3, + maxHeight: maxHeight / 3, + ), + child: Image.memory(value), + ), + ), + ); + }, + ); + } else { + SmartDialog.showToast('截图失败'); + } + }); + }, + ), + ), + ), + ), + ), + ), + ), + + Obx(() { + if (plPlayerController.dataStatus.loading || + plPlayerController.isBuffering.value) { + return Center( + child: GestureDetector( + onTap: plPlayerController.refreshPlayer, + child: Container( + padding: const EdgeInsets.all(30), + decoration: const BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [Colors.black26, Colors.transparent], + ), + ), + child: + Column(mainAxisSize: MainAxisSize.min, children: [ + Image.asset( + 'assets/images/loading.gif', + height: 25, + semanticLabel: "加载中", + ), + if (plPlayerController.isBuffering.value) + Obx(() { + if (plPlayerController.buffered.value == + Duration.zero) { + return const Text('加载中...', + style: TextStyle( + color: Colors.white, fontSize: 12)); + } + String bufferStr = + plPlayerController.buffered.toString(); + return Text( + bufferStr.substring(0, bufferStr.length - 3), + style: const TextStyle( + color: Colors.white, fontSize: 12), + ); + }), + ]), + ))); + } else { + return const SizedBox.shrink(); + } + }), + + /// 点击 快进/快退 + Obx( + () => _mountSeekBackwardButton.value || + _mountSeekForwardButton.value + ? Positioned.fill( + child: Row( + children: [ + if (_mountSeekBackwardButton.value) + Expanded( + child: TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: const Duration(milliseconds: 500), + builder: (context, value, child) => Opacity( + opacity: value, + child: child, + ), + child: BackwardSeekIndicator( + duration: plPlayerController + .fastForBackwardDuration, + onSubmitted: (Duration value) { + _mountSeekBackwardButton.value = false; + final Player player = widget + .plPlayerController + .videoPlayerController!; + Duration result = + player.state.position - value; + result = result.clamp( + Duration.zero, + player.state.duration, + ); + plPlayerController + ..seekTo( + result, + type: 'slider', + ) + ..play(); + }, + ), + ), + ), + const Spacer(flex: 2), + if (_mountSeekForwardButton.value) + Expanded( + child: TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: const Duration(milliseconds: 500), + builder: (context, value, child) => Opacity( + opacity: value, + child: child, + ), + child: ForwardSeekIndicator( + duration: plPlayerController + .fastForBackwardDuration, + onSubmitted: (Duration value) { + _mountSeekForwardButton.value = false; + final Player player = widget + .plPlayerController + .videoPlayerController!; + Duration result = + player.state.position + value; + result = result.clamp( + Duration.zero, + player.state.duration, + ); + plPlayerController + ..seekTo( + result, + type: 'slider', + ) + ..play(); + }, + ), + ), + ), + ], + ), + ) + : const SizedBox.shrink(), + ), + ], + ); + }, ); } } @@ -1785,85 +1805,85 @@ Widget buildDmChart( } Widget buildSeekPreviewWidget(PlPlayerController plPlayerController) { - return Obx(() { - if (!plPlayerController.showPreview.value || - plPlayerController.videoShot?['status'] != true) { - if (plPlayerController.videoShot == null) { - plPlayerController.getVideoShot(); + return Obx( + () { + if (!plPlayerController.showPreview.value || + plPlayerController.videoShot?['status'] != true) { + if (plPlayerController.videoShot == null) { + plPlayerController.getVideoShot(); + } + return const SizedBox.shrink(); } - return const SizedBox.shrink(); - } - VideoShotData data = plPlayerController.videoShot!['data']; + VideoShotData data = plPlayerController.videoShot!['data']; - return LayoutBuilder( - builder: (context, constraints) { - try { - double scale = plPlayerController.isFullScreen.value && - plPlayerController.direction.value == 'horizontal' - ? 4 - : 2.5; - // offset - double left = (plPlayerController.previewDx.value - 48 * scale / 2) - .clamp(8, constraints.maxWidth - 48 * scale - 8); + return LayoutBuilder( + builder: (context, constraints) { + try { + double scale = plPlayerController.isFullScreen.value && + plPlayerController.direction.value == 'horizontal' + ? 4 + : 2.5; + // offset + double left = (plPlayerController.previewDx.value - 48 * scale / 2) + .clamp(8, constraints.maxWidth - 48 * scale - 8); - // index - // int index = plPlayerController.sliderPositionSeconds.value ~/ 5; - int index = max( - 0, - (data.index! - .where((item) => - item <= - plPlayerController.sliderPositionSeconds.value) - .length - - 2)); + // index + // int index = plPlayerController.sliderPositionSeconds.value ~/ 5; + int index = max( + 0, + (data.index! + .where((item) => + item <= + plPlayerController.sliderPositionSeconds.value) + .length - + 2)); - // pageIndex - int pageIndex = (index ~/ 100).clamp(0, data.image!.length - 1); + // pageIndex + int pageIndex = (index ~/ 100).clamp(0, data.image!.length - 1); - // alignment - double cal(m) { - return -1 + 2 / 9 * m; - } + // alignment + double cal(m) { + return -1 + 2 / 9 * m; + } - int align = index % 100; - int x = align % 10; - int y = align ~/ 10; - double dx = cal(x); - double dy = cal(y); - Alignment alignment = Alignment(dx, dy); + int align = index % 100; + int x = align % 10; + int y = align ~/ 10; + double dx = cal(x); + double dy = cal(y); + Alignment alignment = Alignment(dx, dy); - return Container( - alignment: Alignment.centerLeft, - padding: EdgeInsets.only(left: left), - child: UnconstrainedBox( - child: ClipRRect( - borderRadius: scale == 2.5 - ? const BorderRadius.all(Radius.circular(6)) - : StyleString.mdRadius, - child: Align( - widthFactor: 0.1, - heightFactor: 0.1, - alignment: alignment, - child: CachedNetworkImage( - fit: BoxFit.fill, - width: 480 * scale, - height: 270 * scale, - imageUrl: data.image![pageIndex].http2https, + return Container( + alignment: Alignment.centerLeft, + padding: EdgeInsets.only(left: left), + child: UnconstrainedBox( + child: ClipRRect( + borderRadius: scale == 2.5 + ? const BorderRadius.all(Radius.circular(6)) + : StyleString.mdRadius, + child: Align( + widthFactor: 0.1, + heightFactor: 0.1, + alignment: alignment, + child: CachedNetworkImage( + fit: BoxFit.fill, + width: 480 * scale, + height: 270 * scale, + imageUrl: data.image![pageIndex].http2https, + ), ), ), ), - ), - ); - } catch (e) { - if (kDebugMode) debugPrint('seek preview: $e'); - return SizedBox.shrink( - key: ValueKey(plPlayerController.previewDx.value), - ); - } - }, - ); - }); + ); + } catch (e) { + if (kDebugMode) debugPrint('seek preview: $e'); + return const SizedBox.shrink(); + } + }, + ); + }, + ); } Widget buildViewPointWidget(