Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 92 additions & 70 deletions lib/pages/video/widgets/player_focus.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import 'package:flutter/services.dart'
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';

class PlayerFocus extends StatelessWidget {
class PlayerFocus extends StatefulWidget {
const PlayerFocus({
super.key,
required this.child,
Expand All @@ -33,6 +33,23 @@ class PlayerFocus extends StatelessWidget {
final bool Function()? canPlay;
final bool Function()? onSkipSegment;

@override
State<PlayerFocus> createState() => _PlayerFocusState();
}

class _PlayerFocusState extends State<PlayerFocus> {
Timer? _longPressTimer;
Timer? _scrubbingTimer;

@override
void dispose() {
_longPressTimer?.cancel();
_longPressTimer = null;
_scrubbingTimer?.cancel();
_scrubbingTimer = null;
super.dispose();
}

static bool _shouldHandle(LogicalKeyboardKey logicalKey) {
return logicalKey == LogicalKeyboardKey.tab ||
logicalKey == LogicalKeyboardKey.arrowLeft ||
Expand All @@ -52,12 +69,12 @@ class PlayerFocus extends StatelessWidget {
}
return KeyEventResult.ignored;
},
child: child,
child: widget.child,
);
}

bool get isFullScreen => plPlayerController.isFullScreen.value;
bool get hasPlayer => plPlayerController.videoPlayerController != null;
bool get isFullScreen => widget.plPlayerController.isFullScreen.value;
bool get hasPlayer => widget.plPlayerController.videoPlayerController != null;

bool _handleKey(KeyEvent event) {
final key = event.logicalKey;
Expand All @@ -67,11 +84,11 @@ class PlayerFocus extends StatelessWidget {
if (HardwareKeyboard.instance.isMetaPressed) {
return true;
}
if (!plPlayerController.isLive) {
if (!widget.plPlayerController.isLive) {
if (event is KeyDownEvent) {
introController!.onStartTriple();
widget.introController!.onStartTriple();
} else if (event is KeyUpEvent) {
introController!.onCancelTriple(isKeyQ);
widget.introController!.onCancelTriple(isKeyQ);
}
}
return true;
Expand All @@ -81,13 +98,13 @@ class PlayerFocus extends StatelessWidget {
if (isArrowUp || key == LogicalKeyboardKey.arrowDown) {
if (event is! KeyDownEvent) return true;
if (isArrowUp) {
if (introController case final introController?) {
if (widget.introController case final introController?) {
if (!introController.prevPlay()) {
SmartDialog.showToast('已经是第一集了');
}
}
} else {
if (introController case final introController?) {
if (widget.introController case final introController?) {
if (!introController.nextPlay()) {
SmartDialog.showToast('已经是最后一集了');
}
Expand All @@ -96,28 +113,40 @@ class PlayerFocus extends StatelessWidget {
return true;
}

if (key == LogicalKeyboardKey.arrowRight) {
if (!plPlayerController.isLive) {
if (event is KeyDownEvent) {
if (hasPlayer && !plPlayerController.longPressStatus.value) {
plPlayerController
..cancelLongPressTimer()
..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);
final isForward = key == LogicalKeyboardKey.arrowRight;
final isBackward = key == LogicalKeyboardKey.arrowLeft;

if (isForward || isBackward) {
if (widget.plPlayerController.isLive) return true;

if (event is KeyDownEvent) {
if (hasPlayer && _longPressTimer == null) {
_longPressTimer = Timer(const Duration(milliseconds: 300), () {
widget.plPlayerController.startScrubbing();
_scrubbingTimer?.cancel();
_scrubbingTimer =
Timer.periodic(const Duration(milliseconds: 200), (_) {
widget.plPlayerController.updateScrubbing(isForward);
});
});
}
} else if (event is KeyUpEvent) {
_longPressTimer?.cancel();
_longPressTimer = null;
_scrubbingTimer?.cancel();
_scrubbingTimer = null;

if (hasPlayer) {
if (widget.plPlayerController.isScrubbing.value) {
widget.plPlayerController.endScrubbing();
} else {
if (isForward) {
widget.plPlayerController
.onForward(widget.plPlayerController.fastForBackwardDuration);
} else {
plPlayerController.onForward(
plPlayerController.fastForBackwardDuration,
);
// isBackward
widget.plPlayerController.onBackward(
widget.plPlayerController.fastForBackwardDuration);
}
}
}
Expand All @@ -130,8 +159,8 @@ class PlayerFocus extends StatelessWidget {
if (isDigit1 || key == LogicalKeyboardKey.digit2) {
if (HardwareKeyboard.instance.isShiftPressed && hasPlayer) {
final speed = isDigit1 ? 1.0 : 2.0;
if (speed != plPlayerController.playbackSpeed) {
plPlayerController.setPlaybackSpeed(speed);
if (speed != widget.plPlayerController.playbackSpeed) {
widget.plPlayerController.setPlaybackSpeed(speed);
}
SmartDialog.showToast('${speed}x播放');
}
Expand All @@ -140,32 +169,32 @@ class PlayerFocus extends StatelessWidget {

switch (key) {
case LogicalKeyboardKey.space:
if (plPlayerController.isLive || canPlay!()) {
if (widget.plPlayerController.isLive || widget.canPlay!()) {
if (hasPlayer) {
plPlayerController.onDoubleTapCenter();
widget.plPlayerController.onDoubleTapCenter();
}
}
return true;

case LogicalKeyboardKey.keyF:
final isFullScreen = this.isFullScreen;
if (isFullScreen && plPlayerController.controlsLock.value) {
plPlayerController
if (isFullScreen && widget.plPlayerController.controlsLock.value) {
widget.plPlayerController
..controlsLock.value = false
..showControls.value = false;
}
plPlayerController.triggerFullScreen(
widget.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) {
final newVal = !widget.plPlayerController.enableShowDanmaku.value;
widget.plPlayerController.enableShowDanmaku.value = newVal;
if (!widget.plPlayerController.tempPlayerConf) {
GStorage.setting.put(
plPlayerController.isLive
widget.plPlayerController.isLive
? SettingBoxKey.enableShowLiveDanmaku
: SettingBoxKey.enableShowDanmaku,
newVal,
Expand All @@ -175,7 +204,7 @@ class PlayerFocus extends StatelessWidget {

case LogicalKeyboardKey.keyP:
if (Utils.isDesktop && hasPlayer && !isFullScreen) {
plPlayerController
widget.plPlayerController
..toggleDesktopPip()
..controlsLock.value = false
..showControls.value = false;
Expand All @@ -184,90 +213,83 @@ class PlayerFocus extends StatelessWidget {

case LogicalKeyboardKey.keyM:
if (hasPlayer) {
final isMuted = !plPlayerController.isMuted;
plPlayerController.videoPlayerController!.setVolume(
isMuted ? 0 : plPlayerController.volume.value * 100,
final isMuted = !widget.plPlayerController.isMuted;
widget.plPlayerController.videoPlayerController!.setVolume(
isMuted ? 0 : widget.plPlayerController.volume.value * 100,
);
plPlayerController.isMuted = isMuted;
widget.plPlayerController.isMuted = isMuted;
SmartDialog.showToast('${isMuted ? '' : '取消'}静音');
}
return true;

case LogicalKeyboardKey.keyS:
if (hasPlayer && isFullScreen) {
plPlayerController.takeScreenshot();
widget.plPlayerController.takeScreenshot();
}
return true;

case LogicalKeyboardKey.keyL:
if (isFullScreen || plPlayerController.isDesktopPip) {
plPlayerController.onLockControl(
!plPlayerController.controlsLock.value,
if (isFullScreen || widget.plPlayerController.isDesktopPip) {
widget.plPlayerController.onLockControl(
!widget.plPlayerController.controlsLock.value,
);
}
return true;

case LogicalKeyboardKey.enter:
case LogicalKeyboardKey.select:
if (onSkipSegment?.call() ?? false) {
if (widget.onSkipSegment?.call() ?? false) {
return true;
}
if (plPlayerController.isLive || canPlay!()) {
if (widget.plPlayerController.isLive || widget.canPlay!()) {
if (hasPlayer) {
plPlayerController.onDoubleTapCenter();
widget.plPlayerController.onDoubleTapCenter();
}
}
return true;
case LogicalKeyboardKey.contextMenu:
if (plPlayerController.isLive || (canPlay?.call() ?? false)) {
if (widget.plPlayerController.isLive ||
(widget.canPlay?.call() ?? false)) {
if (hasPlayer) {
onShowMenu?.call();
widget.onShowMenu?.call();
}
}
return true;
}

if (!plPlayerController.isLive) {
if (!widget.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();
widget.introController?.actionCoinVideo();
return true;

case LogicalKeyboardKey.keyE:
introController?.actionFavVideo(isQuick: true);
widget.introController?.actionFavVideo(isQuick: true);
return true;

case LogicalKeyboardKey.keyT || LogicalKeyboardKey.keyV:
introController?.viewLater();
widget.introController?.viewLater();
return true;

case LogicalKeyboardKey.keyG:
if (introController case UgcIntroController ugcCtr) {
if (widget.introController case UgcIntroController ugcCtr) {
ugcCtr.actionRelationMod(Get.context!);
}
return true;

case LogicalKeyboardKey.bracketLeft:
if (introController case final introController?) {
if (widget.introController case final introController?) {
if (!introController.prevPlay()) {
SmartDialog.showToast('已经是第一集了');
}
}
return true;

case LogicalKeyboardKey.bracketRight:
if (introController case final introController?) {
if (widget.introController case final introController?) {
if (!introController.nextPlay()) {
SmartDialog.showToast('已经是最后一集了');
}
Expand Down
37 changes: 37 additions & 0 deletions lib/plugin/pl_player/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ class PlPlayerController {
/// 是否长按倍速
final RxBool longPressStatus = false.obs;

/// 是否正在拖拽
final RxBool isScrubbing = false.obs;
final Rx<Duration> scrubbingPosition = Rx(Duration.zero);
int scrubbingSpeed = 1;

/// 屏幕锁 为true时,关闭控制栏
final RxBool controlsLock = false.obs;

Expand Down Expand Up @@ -1896,4 +1901,36 @@ class PlPlayerController {
}
});
}

void startScrubbing() {
if (isLive) return;
isScrubbing.value = true;
scrubbingPosition.value = position.value;
if (showSeekPreview && videoShot == null) {
getVideoShot();
}
videoPlayerController?.pause();
}

void updateScrubbing(bool forward) {
if (!isScrubbing.value) return;

final newPosition = scrubbingPosition.value +
Duration(seconds: forward ? scrubbingSpeed : -scrubbingSpeed);
scrubbingPosition.value = newPosition.clamp(Duration.zero, duration.value);

if (scrubbingSpeed < 64) {
scrubbingSpeed *= 2;
}
}

void endScrubbing() {
if (!isScrubbing.value) return;

seekTo(scrubbingPosition.value).then((_) {
videoPlayerController?.play();
});
isScrubbing.value = false;
scrubbingSpeed = 1;
}
}
3 changes: 3 additions & 0 deletions lib/plugin/pl_player/view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import 'package:PiliPlus/plugin/pl_player/models/gesture_type.dart';
import 'package:PiliPlus/plugin/pl_player/models/play_status.dart';
import 'package:PiliPlus/plugin/pl_player/models/video_fit_type.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/app_bar_ani.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/scrubbing_overlay.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/backward_seek.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/bottom_control.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/common_btn.dart';
Expand Down Expand Up @@ -119,6 +120,7 @@ class PLVideoPlayer extends StatefulWidget {

class _PLVideoPlayerState extends State<PLVideoPlayer>
with WidgetsBindingObserver, TickerProviderStateMixin {
PlPlayerController get controller => widget.plPlayerController;
void onDoubleTapCenter() {}
void onDoubleTapSeekBackward() {}
void onDoubleTapSeekForward() {}
Expand Down Expand Up @@ -1662,6 +1664,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
),
),
),
ScrubbingOverlay(controller: controller),
],
),
),
Expand Down
Loading