diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index b0871b93..6b8c6147 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -13,6 +13,7 @@ import 'package:PiliPlus/models_new/live/live_room_info_h5/data.dart'; import 'package:PiliPlus/models_new/live/live_room_play_info/codec.dart'; import 'package:PiliPlus/models_new/live/live_room_play_info/data.dart'; import 'package:PiliPlus/models_new/live/live_superchat/item.dart'; +import 'package:PiliPlus/pages/live_room/send_danmaku/view.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/models/data_source.dart'; import 'package:PiliPlus/services/service_locator.dart'; @@ -429,4 +430,43 @@ class LiveRoomController extends GetxController { } likeClickTime.value = 0; } + + void onSendDanmaku([bool fromEmote = false]) { + if (!isLogin) { + SmartDialog.showToast('账号未登录'); + return; + } + Get.generalDialog( + barrierLabel: '', + barrierDismissible: true, + pageBuilder: (context, animation, secondaryAnimation) { + return LiveSendDmPanel( + fromEmote: fromEmote, + liveRoomController: this, + items: savedDanmaku, + onSave: (msg) { + if (msg.isEmpty) { + savedDanmaku?.clear(); + savedDanmaku = null; + } else { + savedDanmaku = msg.toList(); + } + }, + ); + }, + transitionDuration: fromEmote + ? const Duration(milliseconds: 400) + : const Duration(milliseconds: 500), + transitionBuilder: (context, animation, secondaryAnimation, child) { + var tween = Tween( + begin: const Offset(0.0, 1.0), + end: Offset.zero, + ).chain(CurveTween(curve: Curves.linear)); + return SlideTransition( + position: animation.drive(tween), + child: child, + ); + }, + ); + } } diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index 8b45fb65..e8dfa452 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -9,13 +9,12 @@ import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/live/live_room_info_h5/data.dart'; import 'package:PiliPlus/models_new/live/live_superchat/item.dart'; import 'package:PiliPlus/pages/live_room/controller.dart'; -import 'package:PiliPlus/pages/live_room/send_danmaku/view.dart'; import 'package:PiliPlus/pages/live_room/superchat/superchat_card.dart'; import 'package:PiliPlus/pages/live_room/superchat/superchat_panel.dart'; import 'package:PiliPlus/pages/live_room/widgets/bottom_control.dart'; import 'package:PiliPlus/pages/live_room/widgets/chat_panel.dart'; import 'package:PiliPlus/pages/live_room/widgets/header_control.dart'; -import 'package:PiliPlus/pages/video/widgets/focus.dart'; +import 'package:PiliPlus/pages/video/widgets/player_focus.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/models/play_status.dart'; import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart'; @@ -182,18 +181,21 @@ class _LiveRoomPageState extends State @override Widget build(BuildContext context) { if (Platform.isAndroid) { - return Floating().isPipMode - ? videoPlayerPanel( - isFullScreen, - width: maxWidth, - height: maxHeight, - isPipMode: true, - needDm: !plPlayerController.pipNoDanmaku, - ) - : focus(childWhenDisabled); - } else { - return focus(childWhenDisabled); + if (Floating().isPipMode) { + return videoPlayerPanel( + isFullScreen, + width: maxWidth, + height: maxHeight, + isPipMode: true, + needDm: !plPlayerController.pipNoDanmaku, + ); + } } + return PlayerFocus( + plPlayerController: plPlayerController, + onSendDanmaku: _liveRoomController.onSendDanmaku, + child: childWhenDisabled, + ); } Widget videoPlayerPanel( @@ -223,7 +225,7 @@ class _LiveRoomPageState extends State title: roomInfoH5?.roomInfo?.title, upName: roomInfoH5?.anchorInfo?.baseInfo?.uname, plPlayerController: plPlayerController, - onSendDanmaku: onSendDanmaku, + onSendDanmaku: _liveRoomController.onSendDanmaku, onPlayAudio: _liveRoomController.queryLiveUrl, ), bottomControl: BottomControl( @@ -762,7 +764,7 @@ class _LiveRoomPageState extends State color: Color(0x1AFFFFFF), ), child: GestureDetector( - onTap: onSendDanmaku, + onTap: _liveRoomController.onSendDanmaku, behavior: HitTestBehavior.opaque, child: Padding( padding: const EdgeInsets.only(top: 5, bottom: 10), @@ -876,7 +878,7 @@ class _LiveRoomPageState extends State height: 34, child: IconButton( style: IconButton.styleFrom(padding: EdgeInsets.zero), - onPressed: () => onSendDanmaku(true), + onPressed: () => _liveRoomController.onSendDanmaku(true), icon: const Icon( size: 22, color: Color(0xFFEEEEEE), @@ -947,46 +949,6 @@ class _LiveRoomPageState extends State } return Colors.transparent; }); - - void onSendDanmaku([bool fromEmote = false]) { - if (!_liveRoomController.isLogin) { - SmartDialog.showToast('账号未登录'); - return; - } - Get.generalDialog( - barrierLabel: '', - barrierDismissible: true, - pageBuilder: (context, animation, secondaryAnimation) { - return LiveSendDmPanel( - fromEmote: fromEmote, - liveRoomController: _liveRoomController, - items: _liveRoomController.savedDanmaku, - onSave: (msg) { - if (msg.isEmpty) { - _liveRoomController - ..savedDanmaku?.clear() - ..savedDanmaku = null; - } else { - _liveRoomController.savedDanmaku = msg.toList(); - } - }, - ); - }, - transitionDuration: fromEmote - ? const Duration(milliseconds: 400) - : const Duration(milliseconds: 500), - transitionBuilder: (context, animation, secondaryAnimation, child) { - var tween = Tween( - begin: const Offset(0.0, 1.0), - end: Offset.zero, - ).chain(CurveTween(curve: Curves.linear)); - return SlideTransition( - position: animation.drive(tween), - child: child, - ); - }, - ); - } } class _BorderClipper extends CustomClipper { diff --git a/lib/pages/video/view.dart b/lib/pages/video/view.dart index 1a047ef6..69462ab0 100644 --- a/lib/pages/video/view.dart +++ b/lib/pages/video/view.dart @@ -34,8 +34,8 @@ import 'package:PiliPlus/pages/video/related/view.dart'; import 'package:PiliPlus/pages/video/reply/controller.dart'; import 'package:PiliPlus/pages/video/reply/view.dart'; import 'package:PiliPlus/pages/video/view_point/view.dart'; -import 'package:PiliPlus/pages/video/widgets/focus.dart'; import 'package:PiliPlus/pages/video/widgets/header_control.dart'; +import 'package:PiliPlus/pages/video/widgets/player_focus.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/models/fullscreen_mode.dart'; import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart'; @@ -1484,7 +1484,14 @@ class _VideoDetailPageVState extends State } else { child = autoChoose(childWhenDisabledAlmostSquare); } - child = focus(child); + child = PlayerFocus( + plPlayerController: videoDetailController.plPlayerController, + introController: videoDetailController.isUgc + ? ugcIntroController + : pgcIntroController, + onSendDanmaku: videoDetailController.showShootDanmakuSheet, + child: child, + ); return videoDetailController.plPlayerController.darkVideoPage ? Theme(data: themeData, child: child) : child; diff --git a/lib/pages/video/widgets/focus.dart b/lib/pages/video/widgets/focus.dart deleted file mode 100644 index b89b463e..00000000 --- a/lib/pages/video/widgets/focus.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart' show LogicalKeyboardKey; - -Widget focus(Widget child) => Focus( - onKeyEvent: (node, event) { - if (event.logicalKey == LogicalKeyboardKey.tab || - event.logicalKey == LogicalKeyboardKey.arrowLeft || - event.logicalKey == LogicalKeyboardKey.arrowRight || - event.logicalKey == LogicalKeyboardKey.arrowUp || - event.logicalKey == LogicalKeyboardKey.arrowDown) { - return KeyEventResult.handled; - } - return KeyEventResult.ignored; - }, - child: child, -); diff --git a/lib/pages/video/widgets/player_focus.dart b/lib/pages/video/widgets/player_focus.dart new file mode 100644 index 00000000..93e8c799 --- /dev/null +++ b/lib/pages/video/widgets/player_focus.dart @@ -0,0 +1,191 @@ +import 'dart:async'; +import 'dart:math' as math; + +import 'package:PiliPlus/pages/common/common_intro_controller.dart'; +import 'package:PiliPlus/pages/video/introduction/ugc/controller.dart'; +import 'package:PiliPlus/plugin/pl_player/controller.dart'; +import 'package:PiliPlus/utils/storage.dart'; +import 'package:PiliPlus/utils/storage_key.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' + show KeyDownEvent, KeyRepeatEvent, KeyUpEvent, LogicalKeyboardKey; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; + +class PlayerFocus extends StatelessWidget { + const PlayerFocus({ + super.key, + required this.child, + required this.plPlayerController, + this.introController, + required this.onSendDanmaku, + }); + + final Widget child; + final PlPlayerController plPlayerController; + final CommonIntroController? introController; + final VoidCallback onSendDanmaku; + + static bool _showHandled(KeyEvent event) { + return event.logicalKey == LogicalKeyboardKey.tab || + event.logicalKey == LogicalKeyboardKey.arrowLeft || + event.logicalKey == LogicalKeyboardKey.arrowRight || + event.logicalKey == LogicalKeyboardKey.arrowUp || + event.logicalKey == LogicalKeyboardKey.arrowDown; + } + + @override + Widget build(BuildContext context) { + return Focus( + autofocus: true, + onKeyEvent: (node, event) { + final handled = _handleKey(event); + if (handled || _showHandled(event)) { + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: child, + ); + } + + bool get isFullScreen => plPlayerController.isFullScreen.value; + bool get hasPlayer => plPlayerController.videoPlayerController != null; + + bool _handleKey(KeyEvent event) { + final key = event.logicalKey; + if (event is KeyDownEvent) { + switch (key) { + case LogicalKeyboardKey.space: + if (hasPlayer) { + plPlayerController.onDoubleTapCenter(); + } + return true; + + case LogicalKeyboardKey.keyF: + plPlayerController.triggerFullScreen(status: !isFullScreen); + return true; + + case LogicalKeyboardKey.escape: + if (isFullScreen) { + plPlayerController.triggerFullScreen(status: false); + } else { + Get.back(); + } + return true; + + case LogicalKeyboardKey.keyD: + final newVal = !plPlayerController.enableShowDanmaku.value; + plPlayerController.enableShowDanmaku.value = newVal; + if (!plPlayerController.tempPlayerConf) { + GStorage.setting.put(SettingBoxKey.enableShowDanmaku, newVal); + } + return true; + + case LogicalKeyboardKey.arrowUp: + if (hasPlayer) { + final volume = math.min(1.0, plPlayerController.volume.value + 0.1); + plPlayerController.setVolume(volume); + } + return true; + + case LogicalKeyboardKey.arrowDown: + if (hasPlayer) { + final volume = math.max(0.0, plPlayerController.volume.value - 0.1); + plPlayerController.setVolume(volume); + } + return true; + + case LogicalKeyboardKey.keyM: + if (hasPlayer) { + final isMuted = !plPlayerController.isMuted; + plPlayerController.videoPlayerController!.setVolume( + isMuted ? 0 : plPlayerController.volume.value * 100, + ); + plPlayerController.isMuted = isMuted; + SmartDialog.showToast('${isMuted ? '' : '取消'}静音'); + } + return true; + + case LogicalKeyboardKey.enter: + onSendDanmaku(); + return true; + } + + if (!plPlayerController.isLive) { + switch (key) { + case LogicalKeyboardKey.arrowLeft: + if (hasPlayer) { + plPlayerController.onBackward( + plPlayerController.fastForBackwardDuration, + ); + } + return true; + + case LogicalKeyboardKey.keyQ: + introController?.actionLikeVideo(); + return true; + + case LogicalKeyboardKey.keyW: + introController?.actionCoinVideo(); + return true; + + case LogicalKeyboardKey.keyE: + introController?.actionFavVideo(isQuick: true); + return true; + + case LogicalKeyboardKey.keyR: + introController?.viewLater(); + return true; + + case LogicalKeyboardKey.keyG: + if (introController case UgcIntroController ugcCtr) { + ugcCtr.actionRelationMod(Get.context!); + } + return true; + + case LogicalKeyboardKey.bracketLeft: + if (introController case final introController?) { + if (!introController.prevPlay()) { + SmartDialog.showToast('已经是第一集了'); + } + } + return true; + + case LogicalKeyboardKey.bracketRight: + if (introController case final introController?) { + if (!introController.nextPlay()) { + SmartDialog.showToast('已经是最后一集了'); + } + } + return true; + } + } + } + + if (key == LogicalKeyboardKey.arrowRight) { + if (!plPlayerController.isLive && hasPlayer) { + if (event is KeyRepeatEvent) { + if (!plPlayerController.longPressStatus.value) { + plPlayerController.longPressTimer ??= Timer( + const Duration(milliseconds: 200), + () => plPlayerController.setLongPressStatus(true), + ); + } + } else if (event is KeyUpEvent) { + plPlayerController.cancelLongPressTimer(); + if (plPlayerController.longPressStatus.value) { + plPlayerController.setLongPressStatus(false); + } else { + plPlayerController.onForward( + plPlayerController.fastForBackwardDuration, + ); + } + } + } + return true; + } + + return false; + } +} diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index f8a237c9..5e2118b6 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -21,6 +21,8 @@ import 'package:PiliPlus/pages/mine/controller.dart'; import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart'; import 'package:PiliPlus/plugin/pl_player/models/data_source.dart'; import 'package:PiliPlus/plugin/pl_player/models/data_status.dart'; +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/heart_beat_type.dart'; import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart'; @@ -286,7 +288,9 @@ class PlPlayerController { late double danmakuStaticDuration = Pref.danmakuStaticDuration; late List speedList = Pref.speedList; late bool enableAutoLongPressSpeed = Pref.enableAutoLongPressSpeed; - late bool enableLongShowControl = Pref.enableLongShowControl; + late final showControlDuration = Pref.enableLongShowControl + ? const Duration(seconds: 30) + : const Duration(seconds: 3); late double subtitleFontScale = Pref.subtitleFontScale; late double subtitleFontScaleFS = Pref.subtitleFontScaleFS; late double danmakuLineHeight = Pref.danmakuLineHeight; @@ -316,7 +320,9 @@ class PlPlayerController { late final enableSlideVolumeBrightness = Pref.enableSlideVolumeBrightness; late final enableSlideFS = Pref.enableSlideFS; late final enableDragSubtitle = Pref.enableDragSubtitle; - late final fastForBackwardDuration = Pref.fastForBackwardDuration; + late final fastForBackwardDuration = Duration( + seconds: Pref.fastForBackwardDuration, + ); late final horizontalSeasonPanel = Pref.horizontalSeasonPanel; late final preInitPlayer = Pref.preInitPlayer; @@ -577,7 +583,7 @@ class PlPlayerController { if (showSeekPreview) { _clearPreview(); } - + cancelLongPressTimer(); if (_videoPlayerController != null && _videoPlayerController!.state.playing) { await pause(notify: false); @@ -1179,8 +1185,7 @@ class PlPlayerController { /// 隐藏控制条 void hideTaskControls() { _timer?.cancel(); - Duration waitingTime = Duration(seconds: enableLongShowControl ? 30 : 3); - _timer = Timer(waitingTime, () { + _timer = Timer(showControlDuration, () { if (!isSliderMoving.value && !tripling) { controls = false; } @@ -1220,21 +1225,31 @@ class PlPlayerController { hideTaskControls(); } + final RxBool volumeIndicator = false.obs; + Timer? volumeTimer; + final RxBool volumeInterceptEventStream = false.obs; + Future setVolume(double volume) async { - if (this.volume.value == volume) { - return; - } - this.volume.value = volume; - try { - if (Utils.isDesktop) { - _videoPlayerController!.setVolume(volume * 100); - } else { - FlutterVolumeController.updateShowSystemUI(false); - await FlutterVolumeController.setVolume(volume); + if (this.volume.value != volume) { + this.volume.value = volume; + try { + if (Utils.isDesktop) { + _videoPlayerController!.setVolume(volume * 100); + } else { + FlutterVolumeController.updateShowSystemUI(false); + await FlutterVolumeController.setVolume(volume); + } + } catch (err) { + if (kDebugMode) debugPrint(err.toString()); } - } catch (err) { - if (kDebugMode) debugPrint(err.toString()); } + volumeIndicator.value = true; + volumeInterceptEventStream.value = true; + volumeTimer?.cancel(); + volumeTimer = Timer(const Duration(milliseconds: 200), () { + volumeIndicator.value = false; + volumeInterceptEventStream.value = false; + }); } void volumeUpdated() { @@ -1300,6 +1315,12 @@ class PlPlayerController { showControls.value = val; } + Timer? longPressTimer; + void cancelLongPressTimer() { + longPressTimer?.cancel(); + longPressTimer = null; + } + /// 设置长按倍速状态 live模式下禁用 Future setLongPressStatus(bool val) async { if (isLive) { @@ -1326,6 +1347,62 @@ class PlPlayerController { } } + // 双击播放、暂停 + Future onDoubleTapCenter() async { + if (videoPlayerController!.state.completed) { + await videoPlayerController!.seek(Duration.zero); + videoPlayerController!.play(); + } else { + videoPlayerController!.playOrPause(); + } + } + + final RxBool mountSeekBackwardButton = false.obs; + final RxBool mountSeekForwardButton = false.obs; + + void onDoubleTapSeekBackward() { + mountSeekBackwardButton.value = true; + } + + void onDoubleTapSeekForward() { + mountSeekForwardButton.value = true; + } + + void onForward(Duration duration) { + onForwardBackward(_position.value + duration); + } + + void onBackward(Duration duration) { + onForwardBackward(_position.value - duration); + } + + void onForwardBackward(Duration duration) { + seekTo( + duration.clamp(Duration.zero, videoPlayerController!.state.duration), + isSeek: false, + ).whenComplete(play); + } + + void doubleTapFuc(DoubleTapType type) { + if (!enableQuickDouble) { + onDoubleTapCenter(); + return; + } + switch (type) { + case DoubleTapType.left: + // 双击左边区域 👈 + onDoubleTapSeekBackward(); + break; + case DoubleTapType.center: + onDoubleTapCenter(); + break; + case DoubleTapType.right: + // 双击右边区域 👈 + onDoubleTapSeekForward(); + break; + } + } + /// 关闭控制栏 void onLockControl(bool val) { feedBack(); @@ -1503,6 +1580,7 @@ class PlPlayerController { Future dispose() async { // 每次减1,最后销毁 + cancelLongPressTimer(); if (_playerCount > 1) { _playerCount -= 1; _heartDuration = 0; diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index ac12ffb1..b18a3b9d 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -23,14 +23,12 @@ import 'package:PiliPlus/models_new/video/video_shot/data.dart'; import 'package:PiliPlus/pages/common/common_intro_controller.dart'; import 'package:PiliPlus/pages/video/controller.dart'; import 'package:PiliPlus/pages/video/introduction/pgc/controller.dart'; -import 'package:PiliPlus/pages/video/introduction/ugc/controller.dart'; import 'package:PiliPlus/pages/video/post_panel/popup_menu_text.dart'; import 'package:PiliPlus/pages/video/post_panel/view.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/models/bottom_control_type.dart'; import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart'; 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'; @@ -59,7 +57,6 @@ import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart' hide ContextExtensionss; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:screen_brightness_platform_interface/screen_brightness_platform_interface.dart'; @@ -111,23 +108,13 @@ class _PLVideoPlayerState extends State late VideoController videoController; late final CommonIntroController introController = widget.introController!; - final GlobalKey _keyboardKey = GlobalKey(); final GlobalKey _playerKey = GlobalKey(); final GlobalKey key = GlobalKey(); - final RxBool _mountSeekBackwardButton = false.obs; - final RxBool _mountSeekForwardButton = false.obs; - final RxDouble _brightnessValue = 0.0.obs; final RxBool _brightnessIndicator = false.obs; Timer? _brightnessTimer; - final RxBool _volumeIndicator = false.obs; - Timer? _volumeTimer; - - // final RxDouble _distance = 0.0.obs; - final RxBool _volumeInterceptEventStream = false.obs; - late FullScreenMode mode; late final RxBool showRestoreScaleBtn = false.obs; @@ -144,45 +131,6 @@ class _PLVideoPlayerState extends State // 阅读器限制 // Timer? _accessibilityDebounce; // double _lastAnnouncedValue = -1; - final FocusNode _focusNode = FocusNode(); - - void onDoubleTapSeekBackward() { - _mountSeekBackwardButton.value = true; - } - - void onDoubleTapSeekForward() { - _mountSeekForwardButton.value = true; - } - - // 双击播放、暂停 - Future onDoubleTapCenter() async { - if (plPlayerController.videoPlayerController!.state.completed) { - await plPlayerController.videoPlayerController!.seek(Duration.zero); - plPlayerController.videoPlayerController!.play(); - } else { - plPlayerController.videoPlayerController!.playOrPause(); - } - } - - void doubleTapFuc(DoubleTapType type) { - if (!plPlayerController.enableQuickDouble) { - onDoubleTapCenter(); - return; - } - switch (type) { - case DoubleTapType.left: - // 双击左边区域 👈 - onDoubleTapSeekBackward(); - break; - case DoubleTapType.center: - onDoubleTapCenter(); - break; - case DoubleTapType.right: - // 双击右边区域 👈 - onDoubleTapSeekForward(); - break; - } - } StreamSubscription? _listener; StreamSubscription? _controlsListener; @@ -217,16 +165,18 @@ class _PLVideoPlayerState extends State plPlayerController.volume.value = (await FlutterVolumeController.getVolume())!; FlutterVolumeController.addListener((double value) { - if (mounted && !_volumeInterceptEventStream.value) { + if (mounted && + !plPlayerController.volumeInterceptEventStream.value) { plPlayerController.volume.value = value; if (Platform.isIOS && !FlutterVolumeController.showSystemUI) { - _volumeIndicator.value = true; - _volumeTimer?.cancel(); - _volumeTimer = Timer(const Duration(milliseconds: 800), () { - if (mounted) { - _volumeIndicator.value = false; - } - }); + plPlayerController + ..volumeIndicator.value = true + ..volumeTimer?.cancel() + ..volumeTimer = Timer(const Duration(milliseconds: 800), () { + if (mounted) { + plPlayerController.volumeIndicator.value = false; + } + }); } } }); @@ -250,19 +200,6 @@ class _PLVideoPlayerState extends State } } - Future setVolume(double value) async { - plPlayerController.setVolume(value); - _volumeIndicator.value = true; - _volumeInterceptEventStream.value = true; - _volumeTimer?.cancel(); - _volumeTimer = Timer(const Duration(milliseconds: 200), () { - if (mounted) { - _volumeIndicator.value = false; - _volumeInterceptEventStream.value = false; - } - }); - } - Future setBrightness(double value) async { try { await ScreenBrightnessPlatform.instance.setApplicationScreenBrightness( @@ -281,7 +218,6 @@ class _PLVideoPlayerState extends State @override void dispose() { - _focusNode.dispose(); _listener?.cancel(); _controlsListener?.cancel(); animationController.dispose(); @@ -981,7 +917,7 @@ class _PLVideoPlayerState extends State 0.0, 1.0, ); - setVolume(volume); + plPlayerController.setVolume(volume); }, ); } @@ -1014,7 +950,7 @@ class _PLVideoPlayerState extends State return; } if (plPlayerController.isLive) { - doubleTapFuc(DoubleTapType.center); + plPlayerController.doubleTapFuc(DoubleTapType.center); return; } final double tapPosition = details.localPosition.dx; @@ -1027,114 +963,10 @@ class _PLVideoPlayerState extends State } else { type = DoubleTapType.right; } - doubleTapFuc(type); + plPlayerController.doubleTapFuc(type); } - void _handleKey(KeyEvent event) { - if (event is KeyDownEvent) { - final key = event.logicalKey; - switch (key) { - case LogicalKeyboardKey.space: - onDoubleTapCenter(); - return; - - case LogicalKeyboardKey.keyF: - plPlayerController.triggerFullScreen(status: !isFullScreen); - return; - - case LogicalKeyboardKey.escape: - if (isFullScreen) { - plPlayerController.triggerFullScreen(status: false); - } else { - Get.back(); - } - return; - - case LogicalKeyboardKey.keyD: - final newVal = !plPlayerController.enableShowDanmaku.value; - plPlayerController.enableShowDanmaku.value = newVal; - if (!plPlayerController.tempPlayerConf) { - GStorage.setting.put(SettingBoxKey.enableShowDanmaku, newVal); - } - return; - - case LogicalKeyboardKey.arrowUp: - final volume = math.min( - 1.0, - plPlayerController.volume.value + 0.1, - ); - setVolume(volume); - return; - - case LogicalKeyboardKey.arrowDown: - final volume = math.max( - 0.0, - plPlayerController.volume.value - 0.1, - ); - setVolume(volume); - return; - - case LogicalKeyboardKey.keyM: - final isMuted = !plPlayerController.isMuted; - plPlayerController.videoPlayerController!.setVolume( - isMuted ? 0 : plPlayerController.volume.value * 100, - ); - plPlayerController.isMuted = isMuted; - SmartDialog.showToast('${isMuted ? '' : '取消'}静音'); - return; - } - - if (!plPlayerController.isLive) { - switch (key) { - case LogicalKeyboardKey.arrowLeft: - onDoubleTapSeekBackward(); - return; - - case LogicalKeyboardKey.arrowRight: - onDoubleTapSeekForward(); - return; - - case LogicalKeyboardKey.keyQ: - introController.actionLikeVideo(); - return; - - case LogicalKeyboardKey.keyW: - introController.actionCoinVideo(); - return; - - case LogicalKeyboardKey.keyE: - introController.actionFavVideo(isQuick: true); - return; - - case LogicalKeyboardKey.keyR: - introController.viewLater(); - return; - - case LogicalKeyboardKey.keyG: - if (introController case UgcIntroController ugcCtr) { - ugcCtr.actionRelationMod(context); - } - return; - - case LogicalKeyboardKey.bracketLeft: - if (!introController.prevPlay()) { - SmartDialog.showToast('已经是第一集了'); - } - return; - - case LogicalKeyboardKey.bracketRight: - if (!introController.nextPlay()) { - SmartDialog.showToast('已经是最后一集了'); - } - return; - - case LogicalKeyboardKey.enter: - widget.videoDetailController?.showShootDanmakuSheet(); - return; - } - } - } - } + final isMobile = Utils.isMobile; @override Widget build(BuildContext context) { @@ -1149,7 +981,7 @@ class _PLVideoPlayerState extends State final isFullScreen = this.isFullScreen; final isLive = plPlayerController.isLive; - Widget buildContent() => Stack( + final child = Stack( fit: StackFit.passthrough, key: _playerKey, children: [ @@ -1182,9 +1014,15 @@ class _PLVideoPlayerState extends State onInteractionEnd: _onInteractionEnd, flipX: plPlayerController.flipX.value, flipY: plPlayerController.flipY.value, - onTap: () => plPlayerController.controls = - !plPlayerController.showControls.value, - onDoubleTapDown: onDoubleTapDown, + onTap: isMobile + ? () => plPlayerController.controls = + !plPlayerController.showControls.value + : plPlayerController.onDoubleTapCenter, + onDoubleTapDown: isMobile + ? onDoubleTapDown + : (_) => plPlayerController.triggerFullScreen( + status: !isFullScreen, + ), onLongPressStart: isLive ? null : (_) => plPlayerController.setLongPressStatus(true), @@ -1315,7 +1153,7 @@ class _PLVideoPlayerState extends State final volume = plPlayerController.volume.value; return AnimatedOpacity( curve: Curves.easeInOut, - opacity: _volumeIndicator.value ? 1.0 : 0.0, + opacity: plPlayerController.volumeIndicator.value ? 1.0 : 0.0, duration: const Duration(milliseconds: 150), child: Container( padding: const EdgeInsets.symmetric( @@ -1795,13 +1633,16 @@ class _PLVideoPlayerState extends State /// 点击 快进/快退 if (!isLive) - Obx( - () => - _mountSeekBackwardButton.value || _mountSeekForwardButton.value + Obx(() { + final mountSeekBackwardButton = + plPlayerController.mountSeekBackwardButton.value; + final mountSeekForwardButton = + plPlayerController.mountSeekForwardButton.value; + return mountSeekBackwardButton || mountSeekForwardButton ? Positioned.fill( child: Row( children: [ - if (_mountSeekBackwardButton.value) + if (mountSeekBackwardButton) Expanded( child: TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), @@ -1814,25 +1655,15 @@ class _PLVideoPlayerState extends State 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, isSeek: false) - ..play(); + ..mountSeekBackwardButton.value = false + ..onBackward(value); }, ), ), ), const Spacer(flex: 2), - if (_mountSeekForwardButton.value) + if (mountSeekForwardButton) Expanded( child: TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), @@ -1845,19 +1676,9 @@ class _PLVideoPlayerState extends State 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, isSeek: false) - ..play(); + ..mountSeekForwardButton.value = false + ..onForward(value); }, ), ), @@ -1865,18 +1686,25 @@ class _PLVideoPlayerState extends State ], ), ) - : const SizedBox.shrink(), - ), + : const SizedBox.shrink(); + }), ], ); - - return KeyboardListener( - key: _keyboardKey, - focusNode: _focusNode, - autofocus: true, - onKeyEvent: _handleKey, - child: buildContent(), - ); + if (!isMobile) { + return MouseRegion( + onEnter: (event) { + plPlayerController.controls = true; + }, + onHover: (event) { + plPlayerController.controls = true; + }, + onExit: (event) { + plPlayerController.controls = false; + }, + child: child, + ); + } + return child; } late final segment = Pair( diff --git a/lib/plugin/pl_player/widgets/backward_seek.dart b/lib/plugin/pl_player/widgets/backward_seek.dart index 8e2bdaf5..fec9c19b 100644 --- a/lib/plugin/pl_player/widgets/backward_seek.dart +++ b/lib/plugin/pl_player/widgets/backward_seek.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; class BackwardSeekIndicator extends StatefulWidget { final ValueChanged onSubmitted; - final int duration; + final Duration duration; const BackwardSeekIndicator({ super.key, @@ -24,7 +24,7 @@ class BackwardSeekIndicatorState extends State { @override void initState() { super.initState(); - duration = Duration(seconds: widget.duration); + duration = widget.duration; timer = Timer(const Duration(milliseconds: 400), () { widget.onSubmitted(duration); }); @@ -42,7 +42,7 @@ class BackwardSeekIndicatorState extends State { widget.onSubmitted(duration); }); setState(() { - duration += Duration(seconds: widget.duration); + duration += widget.duration; }); } diff --git a/lib/plugin/pl_player/widgets/forward_seek.dart b/lib/plugin/pl_player/widgets/forward_seek.dart index 9d60c932..7d53e4bd 100644 --- a/lib/plugin/pl_player/widgets/forward_seek.dart +++ b/lib/plugin/pl_player/widgets/forward_seek.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; class ForwardSeekIndicator extends StatefulWidget { final ValueChanged onSubmitted; - final int duration; + final Duration duration; const ForwardSeekIndicator({ super.key, @@ -24,7 +24,7 @@ class ForwardSeekIndicatorState extends State { @override void initState() { super.initState(); - duration = Duration(seconds: widget.duration); + duration = widget.duration; timer = Timer(const Duration(milliseconds: 400), () { widget.onSubmitted(duration); }); @@ -42,7 +42,7 @@ class ForwardSeekIndicatorState extends State { widget.onSubmitted(duration); }); setState(() { - duration += Duration(seconds: widget.duration); + duration += widget.duration; }); }