Files
PiliPlus/lib/pages/video/widgets/player_focus.dart
My-Responsitories 7276cde48a refa player (#1848)
* tweak

* Reapply "opt: audio uri" (#1833)

This reverts commit 8e726f49b2.

* opt: player

* opt: player

* refa: create player

* refa: player

* opt: UaType

* fix: sb seek preview

* opt: setSub

* fix: screenshot

* opt: unnecessary final player state

* opt: player track

* opt FileSource constructor [skip ci]

* fixes

* fix: dispose player

* fix: quote

* update

* fix: multi ua & remove unused loop

* remove unneeded check [skip ci]

---------

Co-authored-by: dom <githubaccount56556@proton.me>
2026-02-27 15:51:55 +08:00

290 lines
9.1 KiB
Dart

import 'dart:async';
import 'dart:io' show exit, Platform;
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/platform_utils.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, KeyUpEvent, LogicalKeyboardKey, HardwareKeyboard;
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,
this.canPlay,
this.onSkipSegment,
this.onRefresh,
});
final Widget child;
final PlPlayerController plPlayerController;
final CommonIntroController? introController;
final VoidCallback onSendDanmaku;
final ValueGetter<bool>? canPlay;
final ValueGetter<bool>? onSkipSegment;
final VoidCallback? onRefresh;
static bool _shouldHandle(LogicalKeyboardKey logicalKey) {
return logicalKey == LogicalKeyboardKey.tab ||
logicalKey == LogicalKeyboardKey.arrowLeft ||
logicalKey == LogicalKeyboardKey.arrowRight ||
logicalKey == LogicalKeyboardKey.arrowUp ||
logicalKey == LogicalKeyboardKey.arrowDown;
}
@override
Widget build(BuildContext context) {
return Focus(
autofocus: true,
onKeyEvent: (node, event) {
final handled = _handleKey(event);
if (handled || _shouldHandle(event.logicalKey)) {
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
child: child,
);
}
bool get isFullScreen => plPlayerController.isFullScreen.value;
bool get hasPlayer => plPlayerController.videoPlayerController != null;
void _setVolume({required bool isIncrease}) {
final volume = isIncrease
? math.min(
PlPlayerController.maxVolume,
plPlayerController.volume.value + 0.1,
)
: math.max(0.0, plPlayerController.volume.value - 0.1);
plPlayerController.setVolume(volume);
}
void _updateVolume(KeyEvent event, {required bool isIncrease}) {
if (event is KeyDownEvent) {
if (hasPlayer) {
_setVolume(isIncrease: isIncrease);
plPlayerController
..longPressTimer?.cancel()
..longPressTimer = Timer.periodic(
const Duration(milliseconds: 150),
(_) => _setVolume(isIncrease: isIncrease),
);
}
} else if (event is KeyUpEvent) {
plPlayerController.cancelLongPressTimer();
}
}
bool _handleKey(KeyEvent event) {
final key = event.logicalKey;
final isKeyQ = key == LogicalKeyboardKey.keyQ;
if (isKeyQ || key == LogicalKeyboardKey.keyR) {
if (HardwareKeyboard.instance.isMetaPressed) {
if (isKeyQ && Platform.isMacOS) {
exit(0);
}
return true;
}
if (event is KeyDownEvent) {
if (plPlayerController.isLive) {
onRefresh?.call();
} else {
introController!.onStartTriple();
}
} else if (event is KeyUpEvent && !plPlayerController.isLive) {
introController!.onCancelTriple(isKeyQ);
}
return true;
}
final isArrowUp = key == LogicalKeyboardKey.arrowUp;
if (isArrowUp || key == LogicalKeyboardKey.arrowDown) {
_updateVolume(event, isIncrease: isArrowUp);
return true;
}
if (key == LogicalKeyboardKey.arrowRight) {
if (!plPlayerController.isLive) {
if (event is KeyDownEvent) {
if (hasPlayer && !plPlayerController.longPressStatus.value) {
plPlayerController
..longPressTimer?.cancel()
..longPressTimer = Timer(
const Duration(milliseconds: 200),
() => plPlayerController
..cancelLongPressTimer()
..setLongPressStatus(true),
);
}
} else if (event is KeyUpEvent) {
plPlayerController.cancelLongPressTimer();
if (hasPlayer) {
if (plPlayerController.longPressStatus.value) {
plPlayerController.setLongPressStatus(false);
} else {
plPlayerController.onForward(
plPlayerController.fastForBackwardDuration,
);
}
}
}
}
return true;
}
if (event is KeyDownEvent) {
final isDigit1 = key == LogicalKeyboardKey.digit1;
if (isDigit1 || key == LogicalKeyboardKey.digit2) {
if (HardwareKeyboard.instance.isShiftPressed && hasPlayer) {
final speed = isDigit1 ? 1.0 : 2.0;
if (speed != plPlayerController.playbackSpeed) {
plPlayerController.setPlaybackSpeed(speed);
}
SmartDialog.showToast('${speed}x播放');
}
return true;
}
switch (key) {
case LogicalKeyboardKey.space:
if (plPlayerController.isLive || canPlay!()) {
if (hasPlayer) {
plPlayerController.onDoubleTapCenter();
}
}
return true;
case LogicalKeyboardKey.keyF:
final isFullScreen = this.isFullScreen;
if (isFullScreen && plPlayerController.controlsLock.value) {
plPlayerController
..controlsLock.value = false
..showControls.value = false;
}
plPlayerController.triggerFullScreen(
status: !isFullScreen,
inAppFullScreen: HardwareKeyboard.instance.isShiftPressed,
);
return true;
case LogicalKeyboardKey.keyD:
final newVal = !plPlayerController.enableShowDanmaku.value;
plPlayerController.enableShowDanmaku.value = newVal;
if (!plPlayerController.tempPlayerConf) {
GStorage.setting.put(
plPlayerController.isLive
? SettingBoxKey.enableShowLiveDanmaku
: SettingBoxKey.enableShowDanmaku,
newVal,
);
}
return true;
case LogicalKeyboardKey.keyP:
if (PlatformUtils.isDesktop && hasPlayer && !isFullScreen) {
plPlayerController
..toggleDesktopPip()
..controlsLock.value = false
..showControls.value = false;
}
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.keyS:
if (hasPlayer && isFullScreen) {
plPlayerController.takeScreenshot();
}
return true;
case LogicalKeyboardKey.keyL:
if (isFullScreen || plPlayerController.isDesktopPip) {
plPlayerController.onLockControl(
!plPlayerController.controlsLock.value,
);
}
return true;
case LogicalKeyboardKey.enter:
if (onSkipSegment?.call() ?? false) {
return true;
}
onSendDanmaku();
return true;
}
if (!plPlayerController.isLive) {
switch (key) {
case LogicalKeyboardKey.arrowLeft:
if (hasPlayer) {
plPlayerController.onBackward(
plPlayerController.fastForBackwardDuration,
);
}
return true;
case LogicalKeyboardKey.keyW:
if (HardwareKeyboard.instance.isMetaPressed) {
return true;
}
introController?.actionCoinVideo();
return true;
case LogicalKeyboardKey.keyE:
introController?.actionFavVideo(isQuick: true);
return true;
case LogicalKeyboardKey.keyT || LogicalKeyboardKey.keyV:
introController?.viewLater();
return true;
case LogicalKeyboardKey.keyG:
if (introController case final 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;
}
}
}
return false;
}
}