Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-09-21 16:10:03 +08:00
parent f4e3484b01
commit 2efb04f77e
7 changed files with 164 additions and 99 deletions

View File

@@ -225,9 +225,7 @@ abstract class MarqueeRender extends RenderBox
if (_distance > 0) {
updateSize();
_ticker
..createTicker(_onTick)
..initStart();
_ticker.initIfNeeded(_onTick);
} else {
_ticker.cancel();
}
@@ -419,6 +417,13 @@ class ContextSingleTicker implements TickerProvider {
}
}
void initIfNeeded(TickerCallback onTick) {
if (_ticker == null) {
createTicker(onTick);
initStart();
}
}
@override
Ticker createTicker(TickerCallback onTick) {
assert(() {

View File

@@ -378,7 +378,7 @@ class LiveRoomController extends GetxController {
case 'SUPER_CHAT_MESSAGE' when (showSuperChat):
final item = SuperChatItem.fromJson(obj['data']);
superChatMsg.insert(0, item);
if (isFullScreen) {
if (isFullScreen || plPlayerController.isDesktopPip) {
fsSC.value = item;
}
break;

View File

@@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:math';
import 'dart:ui';
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
@@ -211,45 +212,48 @@ class _LiveRoomPageState extends State<LiveRoomPage>
Alignment? alignment,
bool needDm = true,
}) {
if (!isFullScreen) {
if (!isFullScreen && !plPlayerController.isDesktopPip) {
_liveRoomController.fsSC.value = null;
}
_liveRoomController.isFullScreen = isFullScreen;
Widget player = Obx(() {
if (_liveRoomController.isLoaded.value) {
final roomInfoH5 = _liveRoomController.roomInfoH5.value;
return PLVideoPlayer(
key: playerKey,
maxWidth: width,
maxHeight: height,
fill: fill,
alignment: alignment,
plPlayerController: plPlayerController,
headerControl: LiveHeaderControl(
title: roomInfoH5?.roomInfo?.title,
upName: roomInfoH5?.anchorInfo?.baseInfo?.uname,
Widget player = Obx(
key: playerKey,
() {
if (_liveRoomController.isLoaded.value) {
final roomInfoH5 = _liveRoomController.roomInfoH5.value;
return PLVideoPlayer(
maxWidth: width,
maxHeight: height,
fill: fill,
alignment: alignment,
plPlayerController: plPlayerController,
onSendDanmaku: _liveRoomController.onSendDanmaku,
onPlayAudio: _liveRoomController.queryLiveUrl,
),
bottomControl: BottomControl(
plPlayerController: plPlayerController,
liveRoomCtr: _liveRoomController,
onRefresh: _liveRoomController.queryLiveUrl,
),
danmuWidget: !needDm
? null
: LiveDanmaku(
liveRoomController: _liveRoomController,
plPlayerController: plPlayerController,
isFullScreen: isFullScreen,
isPipMode: isPipMode,
),
);
}
return const SizedBox.shrink();
});
if (isFullScreen && _liveRoomController.showSuperChat) {
headerControl: LiveHeaderControl(
title: roomInfoH5?.roomInfo?.title,
upName: roomInfoH5?.anchorInfo?.baseInfo?.uname,
plPlayerController: plPlayerController,
onSendDanmaku: _liveRoomController.onSendDanmaku,
onPlayAudio: _liveRoomController.queryLiveUrl,
),
bottomControl: BottomControl(
plPlayerController: plPlayerController,
liveRoomCtr: _liveRoomController,
onRefresh: _liveRoomController.queryLiveUrl,
),
danmuWidget: !needDm
? null
: LiveDanmaku(
liveRoomController: _liveRoomController,
plPlayerController: plPlayerController,
isFullScreen: isFullScreen,
isPipMode: isPipMode,
),
);
}
return const SizedBox.shrink();
},
);
if (_liveRoomController.showSuperChat &&
(isFullScreen || plPlayerController.isDesktopPip)) {
player = Stack(
clipBehavior: Clip.none,
children: [
@@ -421,6 +425,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
final isFullScreen = this.isFullScreen;
final height = maxWidth * 9 / 16;
final videoHeight = isFullScreen ? maxHeight : height;
final bottomHeight = maxHeight - padding.top - height - kToolbarHeight;
return Column(
children: [
Offstage(
@@ -440,7 +445,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
offstage: isFullScreen,
child: SizedBox(
width: maxWidth,
height: maxHeight - padding.top - height - kToolbarHeight,
height: max(0, bottomHeight),
child: _buildBottomWidget,
),
),

View File

@@ -47,7 +47,7 @@ mixin TripleMixin on GetxController, TickerProvider {
static final _duration = Utils.isMobile
? const Duration(milliseconds: 200)
: const Duration(milliseconds: 230);
: const Duration(milliseconds: 255);
void onStartTriple() {
_timer ??= Timer(_duration, () {

View File

@@ -9,7 +9,7 @@ import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'
show KeyDownEvent, KeyUpEvent, LogicalKeyboardKey;
show KeyDownEvent, KeyUpEvent, LogicalKeyboardKey, HardwareKeyboard;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -131,6 +131,18 @@ class PlayerFocus extends StatelessWidget {
}
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!()) {
@@ -170,8 +182,10 @@ class PlayerFocus extends StatelessWidget {
}
return true;
case LogicalKeyboardKey.keyP when (Utils.isDesktop && hasPlayer):
plPlayerController.toggleDesktopPip();
case LogicalKeyboardKey.keyP:
if (Utils.isDesktop && hasPlayer) {
plPlayerController.toggleDesktopPip();
}
return true;
case LogicalKeyboardKey.keyM:
@@ -185,6 +199,12 @@ class PlayerFocus extends StatelessWidget {
}
return true;
case LogicalKeyboardKey.keyS:
if (hasPlayer && isFullScreen) {
plPlayerController.takeScreenshot();
}
return true;
case LogicalKeyboardKey.enter:
onSendDanmaku();
return true;
@@ -218,6 +238,14 @@ class PlayerFocus extends StatelessWidget {
}
return true;
case LogicalKeyboardKey.keyL:
if (isFullScreen || plPlayerController.isDesktopPip) {
plPlayerController.onLockControl(
!plPlayerController.controlsLock.value,
);
}
return true;
case LogicalKeyboardKey.bracketLeft:
if (introController case final introController?) {
if (!introController.prevPlay()) {

View File

@@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math' show max;
import 'dart:math' show max, min;
import 'dart:ui' as ui;
import 'package:PiliPlus/common/constants.dart';
@@ -32,6 +32,7 @@ import 'package:PiliPlus/services/service_locator.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/image_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart' show PageUtils;
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
@@ -1445,7 +1446,10 @@ class PlPlayerController {
void onLockControl(bool val) {
feedBack();
_controlsLock.value = val;
showControls.value = !val;
if (!val && _showControls.value) {
_showControls.refresh();
}
controls = !val;
}
void toggleFullScreen(bool val) {
@@ -1756,4 +1760,49 @@ class PlPlayerController {
if (kDebugMode) debugPrint('getVideoShot: $e');
}
}
void takeScreenshot() {
SmartDialog.showToast('截图中');
videoPlayerController?.screenshot(format: 'image/png').then((value) {
if (value != null) {
SmartDialog.showToast('点击弹窗保存截图');
Get.dialog(
GestureDetector(
onTap: () {
Get.back();
ImageUtils.saveByteImg(
bytes: value,
fileName: 'screenshot_${ImageUtils.time}',
);
},
child: Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.only(right: 12),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: min(Get.width / 3, 350),
),
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(
width: 5,
color: Get.theme.colorScheme.surface,
),
),
child: Padding(
padding: const EdgeInsets.all(5),
child: Image.memory(value),
),
),
),
),
),
),
);
} else {
SmartDialog.showToast('截图失败');
}
});
}
}

View File

@@ -829,6 +829,17 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
if (cumulativeDelta.distance < 1) return;
if (cumulativeDelta.dx.abs() > 3 * cumulativeDelta.dy.abs()) {
_gestureType = GestureType.horizontal;
if (Utils.isMobile) {
late final isFullScreen = this.isFullScreen;
final progressType = plPlayerController.progressType;
if (progressType == BtmProgressBehavior.alwaysHide ||
(isFullScreen &&
progressType == BtmProgressBehavior.onlyHideFullScreen) ||
(!isFullScreen &&
progressType == BtmProgressBehavior.onlyShowFullScreen)) {
plPlayerController.controls = true;
}
}
} else if (cumulativeDelta.dy.abs() > 3 * cumulativeDelta.dx.abs()) {
if (!plPlayerController.enableSlideVolumeBrightness &&
!plPlayerController.enableSlideFS) {
@@ -1023,6 +1034,20 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
plPlayerController.doubleTapFuc(type);
}
void onTapDesktop() {
if (plPlayerController.controlsLock.value) {
return;
}
plPlayerController.onDoubleTapCenter();
}
void onDoubleTapDesktop([_]) {
if (plPlayerController.controlsLock.value) {
return;
}
plPlayerController.triggerFullScreen(status: !isFullScreen);
}
final isMobile = Utils.isMobile;
@override
@@ -1074,12 +1099,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
onTap: isMobile
? () => plPlayerController.controls =
!plPlayerController.showControls.value
: plPlayerController.onDoubleTapCenter,
onDoubleTapDown: isMobile
? onDoubleTapDown
: (_) => plPlayerController.triggerFullScreen(
status: !isFullScreen,
),
: onTapDesktop,
onDoubleTapDown: isMobile ? onDoubleTapDown : onDoubleTapDesktop,
onLongPressStart: isLive
? null
: (_) => plPlayerController.setLongPressStatus(true),
@@ -1528,7 +1549,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
),
// 锁
if (!isLive && isFullScreen && plPlayerController.showFsLockBtn)
if (!isLive &&
plPlayerController.showFsLockBtn &&
(isFullScreen || plPlayerController.isDesktopPip))
ViewSafeArea(
right: false,
child: Align(
@@ -1597,52 +1620,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
(Platform.isAndroid || kDebugMode) && !isLive
? screenshotWebp
: null,
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: maxWidth / 2,
),
//移除圆角
shape: const RoundedRectangleBorder(),
content: GestureDetector(
onTap: () {
Get.back();
ImageUtils.saveByteImg(
bytes: value,
fileName:
'screenshot_${ImageUtils.time}',
);
},
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: maxWidth / 3,
maxHeight: maxHeight / 3,
),
child: Image.memory(value),
),
),
);
},
);
} else {
SmartDialog.showToast('截图失败');
}
});
},
onTap: plPlayerController.takeScreenshot,
),
),
),