mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-07-07 17:50:13 +08:00
@@ -225,9 +225,7 @@ abstract class MarqueeRender extends RenderBox
|
|||||||
|
|
||||||
if (_distance > 0) {
|
if (_distance > 0) {
|
||||||
updateSize();
|
updateSize();
|
||||||
_ticker
|
_ticker.initIfNeeded(_onTick);
|
||||||
..createTicker(_onTick)
|
|
||||||
..initStart();
|
|
||||||
} else {
|
} else {
|
||||||
_ticker.cancel();
|
_ticker.cancel();
|
||||||
}
|
}
|
||||||
@@ -419,6 +417,13 @@ class ContextSingleTicker implements TickerProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void initIfNeeded(TickerCallback onTick) {
|
||||||
|
if (_ticker == null) {
|
||||||
|
createTicker(onTick);
|
||||||
|
initStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Ticker createTicker(TickerCallback onTick) {
|
Ticker createTicker(TickerCallback onTick) {
|
||||||
assert(() {
|
assert(() {
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ class LiveRoomController extends GetxController {
|
|||||||
case 'SUPER_CHAT_MESSAGE' when (showSuperChat):
|
case 'SUPER_CHAT_MESSAGE' when (showSuperChat):
|
||||||
final item = SuperChatItem.fromJson(obj['data']);
|
final item = SuperChatItem.fromJson(obj['data']);
|
||||||
superChatMsg.insert(0, item);
|
superChatMsg.insert(0, item);
|
||||||
if (isFullScreen) {
|
if (isFullScreen || plPlayerController.isDesktopPip) {
|
||||||
fsSC.value = item;
|
fsSC.value = item;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
|
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
|
||||||
@@ -211,45 +212,48 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
Alignment? alignment,
|
Alignment? alignment,
|
||||||
bool needDm = true,
|
bool needDm = true,
|
||||||
}) {
|
}) {
|
||||||
if (!isFullScreen) {
|
if (!isFullScreen && !plPlayerController.isDesktopPip) {
|
||||||
_liveRoomController.fsSC.value = null;
|
_liveRoomController.fsSC.value = null;
|
||||||
}
|
}
|
||||||
_liveRoomController.isFullScreen = isFullScreen;
|
_liveRoomController.isFullScreen = isFullScreen;
|
||||||
Widget player = Obx(() {
|
Widget player = Obx(
|
||||||
if (_liveRoomController.isLoaded.value) {
|
key: playerKey,
|
||||||
final roomInfoH5 = _liveRoomController.roomInfoH5.value;
|
() {
|
||||||
return PLVideoPlayer(
|
if (_liveRoomController.isLoaded.value) {
|
||||||
key: playerKey,
|
final roomInfoH5 = _liveRoomController.roomInfoH5.value;
|
||||||
maxWidth: width,
|
return PLVideoPlayer(
|
||||||
maxHeight: height,
|
maxWidth: width,
|
||||||
fill: fill,
|
maxHeight: height,
|
||||||
alignment: alignment,
|
fill: fill,
|
||||||
plPlayerController: plPlayerController,
|
alignment: alignment,
|
||||||
headerControl: LiveHeaderControl(
|
|
||||||
title: roomInfoH5?.roomInfo?.title,
|
|
||||||
upName: roomInfoH5?.anchorInfo?.baseInfo?.uname,
|
|
||||||
plPlayerController: plPlayerController,
|
plPlayerController: plPlayerController,
|
||||||
onSendDanmaku: _liveRoomController.onSendDanmaku,
|
headerControl: LiveHeaderControl(
|
||||||
onPlayAudio: _liveRoomController.queryLiveUrl,
|
title: roomInfoH5?.roomInfo?.title,
|
||||||
),
|
upName: roomInfoH5?.anchorInfo?.baseInfo?.uname,
|
||||||
bottomControl: BottomControl(
|
plPlayerController: plPlayerController,
|
||||||
plPlayerController: plPlayerController,
|
onSendDanmaku: _liveRoomController.onSendDanmaku,
|
||||||
liveRoomCtr: _liveRoomController,
|
onPlayAudio: _liveRoomController.queryLiveUrl,
|
||||||
onRefresh: _liveRoomController.queryLiveUrl,
|
),
|
||||||
),
|
bottomControl: BottomControl(
|
||||||
danmuWidget: !needDm
|
plPlayerController: plPlayerController,
|
||||||
? null
|
liveRoomCtr: _liveRoomController,
|
||||||
: LiveDanmaku(
|
onRefresh: _liveRoomController.queryLiveUrl,
|
||||||
liveRoomController: _liveRoomController,
|
),
|
||||||
plPlayerController: plPlayerController,
|
danmuWidget: !needDm
|
||||||
isFullScreen: isFullScreen,
|
? null
|
||||||
isPipMode: isPipMode,
|
: LiveDanmaku(
|
||||||
),
|
liveRoomController: _liveRoomController,
|
||||||
);
|
plPlayerController: plPlayerController,
|
||||||
}
|
isFullScreen: isFullScreen,
|
||||||
return const SizedBox.shrink();
|
isPipMode: isPipMode,
|
||||||
});
|
),
|
||||||
if (isFullScreen && _liveRoomController.showSuperChat) {
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (_liveRoomController.showSuperChat &&
|
||||||
|
(isFullScreen || plPlayerController.isDesktopPip)) {
|
||||||
player = Stack(
|
player = Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
@@ -421,6 +425,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
final isFullScreen = this.isFullScreen;
|
final isFullScreen = this.isFullScreen;
|
||||||
final height = maxWidth * 9 / 16;
|
final height = maxWidth * 9 / 16;
|
||||||
final videoHeight = isFullScreen ? maxHeight : height;
|
final videoHeight = isFullScreen ? maxHeight : height;
|
||||||
|
final bottomHeight = maxHeight - padding.top - height - kToolbarHeight;
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Offstage(
|
Offstage(
|
||||||
@@ -440,7 +445,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
|||||||
offstage: isFullScreen,
|
offstage: isFullScreen,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: maxWidth,
|
width: maxWidth,
|
||||||
height: maxHeight - padding.top - height - kToolbarHeight,
|
height: max(0, bottomHeight),
|
||||||
child: _buildBottomWidget,
|
child: _buildBottomWidget,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ mixin TripleMixin on GetxController, TickerProvider {
|
|||||||
|
|
||||||
static final _duration = Utils.isMobile
|
static final _duration = Utils.isMobile
|
||||||
? const Duration(milliseconds: 200)
|
? const Duration(milliseconds: 200)
|
||||||
: const Duration(milliseconds: 230);
|
: const Duration(milliseconds: 255);
|
||||||
|
|
||||||
void onStartTriple() {
|
void onStartTriple() {
|
||||||
_timer ??= Timer(_duration, () {
|
_timer ??= Timer(_duration, () {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import 'package:PiliPlus/utils/storage_key.dart';
|
|||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.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:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -131,6 +131,18 @@ class PlayerFocus extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event is KeyDownEvent) {
|
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) {
|
switch (key) {
|
||||||
case LogicalKeyboardKey.space:
|
case LogicalKeyboardKey.space:
|
||||||
if (plPlayerController.isLive || canPlay!()) {
|
if (plPlayerController.isLive || canPlay!()) {
|
||||||
@@ -170,8 +182,10 @@ class PlayerFocus extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case LogicalKeyboardKey.keyP when (Utils.isDesktop && hasPlayer):
|
case LogicalKeyboardKey.keyP:
|
||||||
plPlayerController.toggleDesktopPip();
|
if (Utils.isDesktop && hasPlayer) {
|
||||||
|
plPlayerController.toggleDesktopPip();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case LogicalKeyboardKey.keyM:
|
case LogicalKeyboardKey.keyM:
|
||||||
@@ -185,6 +199,12 @@ class PlayerFocus extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case LogicalKeyboardKey.keyS:
|
||||||
|
if (hasPlayer && isFullScreen) {
|
||||||
|
plPlayerController.takeScreenshot();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
case LogicalKeyboardKey.enter:
|
case LogicalKeyboardKey.enter:
|
||||||
onSendDanmaku();
|
onSendDanmaku();
|
||||||
return true;
|
return true;
|
||||||
@@ -218,6 +238,14 @@ class PlayerFocus extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case LogicalKeyboardKey.keyL:
|
||||||
|
if (isFullScreen || plPlayerController.isDesktopPip) {
|
||||||
|
plPlayerController.onLockControl(
|
||||||
|
!plPlayerController.controlsLock.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
case LogicalKeyboardKey.bracketLeft:
|
case LogicalKeyboardKey.bracketLeft:
|
||||||
if (introController case final introController?) {
|
if (introController case final introController?) {
|
||||||
if (!introController.prevPlay()) {
|
if (!introController.prevPlay()) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' show max;
|
import 'dart:math' show max, min;
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:PiliPlus/common/constants.dart';
|
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/accounts.dart';
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:PiliPlus/utils/feed_back.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/page_utils.dart' show PageUtils;
|
||||||
import 'package:PiliPlus/utils/storage.dart';
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
import 'package:PiliPlus/utils/storage_key.dart';
|
import 'package:PiliPlus/utils/storage_key.dart';
|
||||||
@@ -1445,7 +1446,10 @@ class PlPlayerController {
|
|||||||
void onLockControl(bool val) {
|
void onLockControl(bool val) {
|
||||||
feedBack();
|
feedBack();
|
||||||
_controlsLock.value = val;
|
_controlsLock.value = val;
|
||||||
showControls.value = !val;
|
if (!val && _showControls.value) {
|
||||||
|
_showControls.refresh();
|
||||||
|
}
|
||||||
|
controls = !val;
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleFullScreen(bool val) {
|
void toggleFullScreen(bool val) {
|
||||||
@@ -1756,4 +1760,49 @@ class PlPlayerController {
|
|||||||
if (kDebugMode) debugPrint('getVideoShot: $e');
|
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.distance < 1) return;
|
||||||
if (cumulativeDelta.dx.abs() > 3 * cumulativeDelta.dy.abs()) {
|
if (cumulativeDelta.dx.abs() > 3 * cumulativeDelta.dy.abs()) {
|
||||||
_gestureType = GestureType.horizontal;
|
_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()) {
|
} else if (cumulativeDelta.dy.abs() > 3 * cumulativeDelta.dx.abs()) {
|
||||||
if (!plPlayerController.enableSlideVolumeBrightness &&
|
if (!plPlayerController.enableSlideVolumeBrightness &&
|
||||||
!plPlayerController.enableSlideFS) {
|
!plPlayerController.enableSlideFS) {
|
||||||
@@ -1023,6 +1034,20 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
plPlayerController.doubleTapFuc(type);
|
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;
|
final isMobile = Utils.isMobile;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -1074,12 +1099,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
onTap: isMobile
|
onTap: isMobile
|
||||||
? () => plPlayerController.controls =
|
? () => plPlayerController.controls =
|
||||||
!plPlayerController.showControls.value
|
!plPlayerController.showControls.value
|
||||||
: plPlayerController.onDoubleTapCenter,
|
: onTapDesktop,
|
||||||
onDoubleTapDown: isMobile
|
onDoubleTapDown: isMobile ? onDoubleTapDown : onDoubleTapDesktop,
|
||||||
? onDoubleTapDown
|
|
||||||
: (_) => plPlayerController.triggerFullScreen(
|
|
||||||
status: !isFullScreen,
|
|
||||||
),
|
|
||||||
onLongPressStart: isLive
|
onLongPressStart: isLive
|
||||||
? null
|
? null
|
||||||
: (_) => plPlayerController.setLongPressStatus(true),
|
: (_) => 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(
|
ViewSafeArea(
|
||||||
right: false,
|
right: false,
|
||||||
child: Align(
|
child: Align(
|
||||||
@@ -1597,52 +1620,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
(Platform.isAndroid || kDebugMode) && !isLive
|
(Platform.isAndroid || kDebugMode) && !isLive
|
||||||
? screenshotWebp
|
? screenshotWebp
|
||||||
: null,
|
: null,
|
||||||
onTap: () {
|
onTap: plPlayerController.takeScreenshot,
|
||||||
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('截图失败');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user