mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-13 05:27:41 +08:00
@@ -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(() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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, () {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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('截图失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user