Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-01-22 11:00:45 +08:00
parent 05e8ded86a
commit 92e5fae29c
6 changed files with 247 additions and 260 deletions

View File

@@ -7,7 +7,6 @@ import 'package:PiliPlus/common/widgets/mouse_back.dart';
import 'package:PiliPlus/common/widgets/scale_app.dart';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/models/common/theme/theme_color_type.dart';
import 'package:PiliPlus/plugin/pl_player/controller.dart';
import 'package:PiliPlus/router/app_pages.dart';
import 'package:PiliPlus/services/account_service.dart';
import 'package:PiliPlus/services/download/download_service.dart';
@@ -231,33 +230,18 @@ class MyApp extends StatelessWidget {
return;
}
if (Get.routing.route is! GetPageRoute) {
Get.back();
return;
}
final plCtr = PlPlayerController.instance;
if (plCtr != null) {
if (plCtr.isFullScreen.value) {
plCtr
..triggerFullScreen(status: false)
..controlsLock.value = false
..showControls.value = false;
return;
}
if (plCtr.isDesktopPip) {
plCtr
..exitDesktopPip().whenComplete(
() => plCtr.initialFocalPoint = Offset.zero,
)
..controlsLock.value = false
..showControls.value = false;
final route = Get.routing.route;
if (route is GetPageRoute) {
if (route.popDisposition == .doNotPop) {
route.onPopInvokedWithResult(false, null);
return;
}
}
Get.back();
final navigator = Get.key.currentState;
if (navigator?.canPop() ?? false) {
navigator!.pop();
}
}
@override

View File

@@ -352,16 +352,8 @@ class _LiveRoomPageState extends State<LiveRoomPage>
);
}
return PopScope(
canPop: !isFullScreen,
onPopInvokedWithResult: (bool didPop, Object? result) {
if (plPlayerController.controlsLock.value) {
plPlayerController.onLockControl(false);
return;
}
if (isFullScreen) {
plPlayerController.triggerFullScreen(status: false);
}
},
canPop: !isFullScreen && !plPlayerController.isDesktopPip,
onPopInvokedWithResult: plPlayerController.onPopInvokedWithResult,
child: player,
);
}

View File

@@ -1343,42 +1343,49 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
required double width,
required double height,
bool isPipMode = false,
}) => Obx(
key: videoDetailController.videoPlayerKey,
() =>
videoDetailController.videoState.value is! Success ||
!videoDetailController.autoPlay.value ||
plPlayerController?.videoController == null
? const SizedBox.shrink()
: PLVideoPlayer(
maxWidth: width,
maxHeight: height,
plPlayerController: plPlayerController!,
videoDetailController: videoDetailController,
introController: introController,
headerControl: HeaderControl(
key: videoDetailController.headerCtrKey,
isPortrait: isPortrait,
controller: videoDetailController.plPlayerController,
videoDetailCtr: videoDetailController,
heroTag: heroTag,
),
danmuWidget: isPipMode && pipNoDanmaku
? null
: Obx(
() => PlDanmaku(
key: ValueKey(videoDetailController.cid.value),
isPipMode: isPipMode,
cid: videoDetailController.cid.value,
playerController: plPlayerController!,
isFullScreen: plPlayerController!.isFullScreen.value,
isFileSource: videoDetailController.isFileSource,
size: Size(width, height),
}) => PopScope(
canPop:
!isFullScreen &&
!videoDetailController.plPlayerController.isDesktopPip &&
(videoDetailController.horizontalScreen || isPortrait),
onPopInvokedWithResult: _onPopInvokedWithResult,
child: Obx(
key: videoDetailController.videoPlayerKey,
() =>
videoDetailController.videoState.value is! Success ||
!videoDetailController.autoPlay.value ||
plPlayerController?.videoController == null
? const SizedBox.shrink()
: PLVideoPlayer(
maxWidth: width,
maxHeight: height,
plPlayerController: plPlayerController!,
videoDetailController: videoDetailController,
introController: introController,
headerControl: HeaderControl(
key: videoDetailController.headerCtrKey,
isPortrait: isPortrait,
controller: videoDetailController.plPlayerController,
videoDetailCtr: videoDetailController,
heroTag: heroTag,
),
danmuWidget: isPipMode && pipNoDanmaku
? null
: Obx(
() => PlDanmaku(
key: ValueKey(videoDetailController.cid.value),
isPipMode: isPipMode,
cid: videoDetailController.cid.value,
playerController: plPlayerController!,
isFullScreen: plPlayerController!.isFullScreen.value,
isFileSource: videoDetailController.isFileSource,
size: Size(width, height),
),
),
),
showEpisodes: showEpisodes,
showViewPoints: showViewPoints,
),
showEpisodes: showEpisodes,
showViewPoints: showViewPoints,
),
),
);
late ThemeData themeData;
@@ -1577,165 +1584,158 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
Widget videoPlayer({required double width, required double height}) {
final isFullScreen = this.isFullScreen;
return PopScope(
canPop:
!isFullScreen &&
(videoDetailController.horizontalScreen || isPortrait),
onPopInvokedWithResult: _onPopInvokedWithResult,
child: Stack(
clipBehavior: Clip.none,
children: [
const Positioned.fill(child: ColoredBox(color: Colors.black)),
return Stack(
clipBehavior: Clip.none,
children: [
const Positioned.fill(child: ColoredBox(color: Colors.black)),
if (isShowing) plPlayer(width: width, height: height),
if (isShowing) plPlayer(width: width, height: height),
Obx(() {
if (!videoDetailController.autoPlay.value) {
return Positioned.fill(
child: GestureDetector(
onTap: handlePlay,
behavior: .opaque,
child: Obx(
() => NetworkImgLayer(
type: .emote,
quality: 60,
src: videoDetailController.cover.value,
width: width,
height: height,
cacheWidth: true,
getPlaceHolder: () => Center(
child: Image.asset('assets/images/loading.png'),
),
Obx(() {
if (!videoDetailController.autoPlay.value) {
return Positioned.fill(
child: GestureDetector(
onTap: handlePlay,
behavior: .opaque,
child: Obx(
() => NetworkImgLayer(
type: .emote,
quality: 60,
src: videoDetailController.cover.value,
width: width,
height: height,
cacheWidth: true,
getPlaceHolder: () => Center(
child: Image.asset('assets/images/loading.png'),
),
),
),
);
),
);
}
return const SizedBox.shrink();
}),
manualPlayerWidget,
if (videoDetailController.plPlayerController.enableBlock ||
videoDetailController.continuePlayingPart)
Positioned(
left: 16,
bottom: isFullScreen ? max(75, maxHeight * 0.25) : 75,
width: MediaQuery.textScalerOf(context).scale(120),
child: AnimatedList(
padding: EdgeInsets.zero,
key: videoDetailController.listKey,
reverse: true,
shrinkWrap: true,
initialItemCount: videoDetailController.listData.length,
itemBuilder: (context, index, animation) {
return videoDetailController.buildItem(
videoDetailController.listData[index],
animation,
);
},
),
),
// for debug
// Positioned(
// right: 16,
// bottom: 75,
// child: FilledButton.tonal(
// onPressed: () {
// videoDetailController.onAddItem(
// SegmentModel(
// UUID: '',
// segmentType:
// SegmentType.values[Utils.random.nextInt(
// SegmentType.values.length,
// )],
// segment: Pair(first: 0, second: 0),
// skipType: SkipType.alwaysSkip,
// ),
// );
// },
// child: const Text('skip'),
// ),
// ),
// Positioned(
// right: 16,
// bottom: 120,
// child: FilledButton.tonal(
// onPressed: () {
// videoDetailController.onAddItem(2);
// },
// child: const Text('index'),
// ),
// ),
Obx(
() {
if (videoDetailController.showSteinEdgeInfo.value) {
try {
return Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: EdgeInsets.only(
left: 16,
right: 16,
bottom: plPlayerController?.showControls.value == true
? 75
: 16,
),
child: Wrap(
spacing: 25,
runSpacing: 10,
children: videoDetailController
.steinEdgeInfo!
.edges!
.questions!
.first
.choices!
.map((item) {
return FilledButton.tonal(
style: FilledButton.styleFrom(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(6),
),
),
backgroundColor: themeData
.colorScheme
.secondaryContainer
.withValues(alpha: 0.8),
padding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 10,
),
visualDensity: VisualDensity.compact,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
ugcIntroController.onChangeEpisode(
item,
isStein: true,
);
videoDetailController.getSteinEdgeInfo(
item.id,
);
},
child: Text(item.option!),
);
})
.toList(),
),
),
);
} catch (e) {
if (kDebugMode) debugPrint('build stein edges: $e');
return const SizedBox.shrink();
}
}
return const SizedBox.shrink();
}),
manualPlayerWidget,
if (videoDetailController.plPlayerController.enableBlock ||
videoDetailController.continuePlayingPart)
Positioned(
left: 16,
bottom: isFullScreen ? max(75, maxHeight * 0.25) : 75,
width: MediaQuery.textScalerOf(context).scale(120),
child: AnimatedList(
padding: EdgeInsets.zero,
key: videoDetailController.listKey,
reverse: true,
shrinkWrap: true,
initialItemCount: videoDetailController.listData.length,
itemBuilder: (context, index, animation) {
return videoDetailController.buildItem(
videoDetailController.listData[index],
animation,
);
},
),
),
// for debug
// Positioned(
// right: 16,
// bottom: 75,
// child: FilledButton.tonal(
// onPressed: () {
// videoDetailController.onAddItem(
// SegmentModel(
// UUID: '',
// segmentType:
// SegmentType.values[Utils.random.nextInt(
// SegmentType.values.length,
// )],
// segment: Pair(first: 0, second: 0),
// skipType: SkipType.alwaysSkip,
// ),
// );
// },
// child: const Text('skip'),
// ),
// ),
// Positioned(
// right: 16,
// bottom: 120,
// child: FilledButton.tonal(
// onPressed: () {
// videoDetailController.onAddItem(2);
// },
// child: const Text('index'),
// ),
// ),
Obx(
() {
if (videoDetailController.showSteinEdgeInfo.value) {
try {
return Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: EdgeInsets.only(
left: 16,
right: 16,
bottom: plPlayerController?.showControls.value == true
? 75
: 16,
),
child: Wrap(
spacing: 25,
runSpacing: 10,
children: videoDetailController
.steinEdgeInfo!
.edges!
.questions!
.first
.choices!
.map((item) {
return FilledButton.tonal(
style: FilledButton.styleFrom(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(6),
),
),
backgroundColor: themeData
.colorScheme
.secondaryContainer
.withValues(alpha: 0.8),
padding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 10,
),
visualDensity: VisualDensity.compact,
tapTargetSize:
MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
ugcIntroController.onChangeEpisode(
item,
isStein: true,
);
videoDetailController.getSteinEdgeInfo(
item.id,
);
},
child: Text(item.option!),
);
})
.toList(),
),
),
);
} catch (e) {
if (kDebugMode) debugPrint('build stein edges: $e');
return const SizedBox.shrink();
}
}
return const SizedBox.shrink();
},
),
],
),
},
),
],
);
}
@@ -2183,20 +2183,13 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
}
void _onPopInvokedWithResult(bool didPop, result) {
if (didPop) {
videoDetailController.plPlayerController.disableAutoEnterPipIfNeeded();
}
if (plPlayerController?.controlsLock.value == true) {
plPlayerController?.onLockControl(false);
if (plPlayerController?.onPopInvokedWithResult(didPop, result) ?? false) {
return;
}
if (isFullScreen) {
videoDetailController.plPlayerController.triggerFullScreen(status: false);
return;
}
if (!videoDetailController.horizontalScreen && !isPortrait) {
if (PlatformUtils.isMobile &&
!videoDetailController.horizontalScreen &&
!isPortrait) {
verticalScreenForTwoSeconds();
return;
}
}

View File

@@ -1848,11 +1848,13 @@ class HeaderControlState extends State<HeaderControl>
color: Colors.white,
),
onPressed: () {
if (plPlayerController.isDesktopPip) {
plPlayerController.exitDesktopPip();
} else if (isFullScreen) {
plPlayerController.triggerFullScreen(status: false);
} else if (PlatformUtils.isMobile &&
if (plPlayerController.onPopInvokedWithResult(
false,
null,
)) {
return;
}
if (PlatformUtils.isMobile &&
!horizontalScreen &&
!isPortrait) {
verticalScreenForTwoSeconds();

View File

@@ -1,6 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:async' show StreamSubscription, Timer;
import 'dart:convert' show ascii;
import 'dart:io' show Platform, File, Directory;
import 'dart:math' show max, min;
import 'dart:ui' as ui;
@@ -36,7 +36,7 @@ import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/extension/string_ext.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';
import 'package:PiliPlus/utils/path_utils.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
@@ -50,7 +50,8 @@ import 'package:easy_debounce/easy_throttle.dart';
import 'package:floating/floating.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/services.dart'
show rootBundle, HapticFeedback, Uint8List;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
import 'package:get/get.dart';
@@ -235,8 +236,6 @@ class PlPlayerController {
return windowManager.setAlwaysOnTop(value);
}
Offset initialFocalPoint = Offset.zero;
Future<void> exitDesktopPip() {
isDesktopPip = false;
return Future.wait([
@@ -307,13 +306,13 @@ class PlPlayerController {
}
}
void disableAutoEnterPipIfNeeded() {
void _disableAutoEnterPipIfNeeded() {
if (!_isPreviousVideoPage) {
disableAutoEnterPip();
_disableAutoEnterPip();
}
}
void disableAutoEnterPip() {
void _disableAutoEnterPip() {
if (_shouldSetPip) {
Utils.channel.invokeMethod('setPipAutoEnterEnabled', {
'autoEnable': false,
@@ -1003,12 +1002,12 @@ class PlPlayerController {
if (_isCurrVideoPage) {
enterPip(isAuto: true);
} else {
disableAutoEnterPip();
_disableAutoEnterPip();
}
}
playerStatus.value = PlayerStatus.playing;
} else {
disableAutoEnterPip();
_disableAutoEnterPip();
playerStatus.value = PlayerStatus.paused;
}
videoPlayerServiceHandler?.onStatusChange(
@@ -1698,7 +1697,7 @@ class PlPlayerController {
danmakuController = null;
_stopListenerForVideoFit();
_stopListenerForEnterFullScreen();
disableAutoEnterPip();
_disableAutoEnterPip();
setPlayCallBack(null);
dmState.clear();
if (showSeekPreview) {
@@ -1873,4 +1872,23 @@ class PlPlayerController {
}
});
}
bool onPopInvokedWithResult(bool didPop, Object? result) {
if (Platform.isAndroid && didPop) {
_disableAutoEnterPipIfNeeded();
}
if (controlsLock.value) {
onLockControl(false);
return true;
}
if (isDesktopPip) {
exitDesktopPip();
return true;
}
if (isFullScreen.value) {
triggerFullScreen(status: false);
return true;
}
return false;
}
}

View File

@@ -140,6 +140,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
GestureType? _gestureType;
Offset initialFocalPoint = Offset.zero;
//播放器放缩
bool interacting = false;
@@ -941,7 +943,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
if (details.pointerCount > 1) {
interacting = true;
}
plPlayerController.initialFocalPoint = localFocalPoint;
initialFocalPoint = localFocalPoint;
// if (kDebugMode) {
// debugPrint("_initialFocalPoint$_initialFocalPoint");
// }
@@ -951,11 +953,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
void _onInteractionUpdate(ScaleUpdateDetails details) {
showRestoreScaleBtn.value =
transformationController.value.storage[0] != 1.0;
if (interacting || plPlayerController.initialFocalPoint == Offset.zero) {
if (interacting || initialFocalPoint == Offset.zero) {
return;
}
Offset cumulativeDelta =
details.localFocalPoint - plPlayerController.initialFocalPoint;
Offset cumulativeDelta = details.localFocalPoint - initialFocalPoint;
if (details.pointerCount > 1 && cumulativeDelta.distanceSquared < 2.25) {
interacting = true;
_gestureType = null;
@@ -1083,8 +1084,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
} else if (_gestureType == GestureType.center) {
// 全屏
const double threshold = 2.5; // 滑动阈值
double cumulativeDy =
details.localFocalPoint.dy - plPlayerController.initialFocalPoint.dy;
double cumulativeDy = details.localFocalPoint.dy - initialFocalPoint.dy;
void fullScreenTrigger(bool status) {
plPlayerController.triggerFullScreen(status: status);
@@ -1143,7 +1143,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
plPlayerController.onChangedSliderEnd();
}
interacting = false;
plPlayerController.initialFocalPoint = Offset.zero;
initialFocalPoint = Offset.zero;
_gestureType = null;
}
@@ -1261,9 +1261,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
status: !isFullScreen,
inAppFullScreen: isSecondaryBtn,
)
.whenComplete(
() => plPlayerController.initialFocalPoint = Offset.zero,
);
.whenComplete(() => initialFocalPoint = Offset.zero);
return;
}
}