diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart index 8f9d82263..6d74c5b32 100644 --- a/lib/pages/setting/play_setting.dart +++ b/lib/pages/setting/play_setting.dart @@ -78,6 +78,18 @@ class _PlaySettingState extends State { setKey: SettingBoxKey.enableAutoBrightness, defaultVal: false, ), + const SetSwitchItem( + title: '自动全屏', + subTitle: '视频开始播放时进入全屏', + setKey: SettingBoxKey.enableAutoEnter, + defaultVal: false, + ), + const SetSwitchItem( + title: '自动退出', + subTitle: '视频结束播放时退出全屏', + setKey: SettingBoxKey.enableAutoExit, + defaultVal: false, + ), ListTile( dense: false, title: Text('默认画质', style: titleStyle), diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 53f0e16de..27cf35c20 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -73,6 +73,7 @@ class VideoDetailController extends GetxController // 默认记录历史记录 bool enableHeart = true; var userInfo; + late bool isFirstTime = true; @override void onInit() { @@ -193,6 +194,7 @@ class VideoDetailController extends GetxController bvid: bvid, cid: cid, enableHeart: enableHeart, + isFirstTime: isFirstTime, ); } @@ -233,7 +235,6 @@ class VideoDetailController extends GetxController currentDecodeFormats = VideoDecodeFormatsCode.fromString(setting.get( SettingBoxKey.defaultDecode, defaultValue: VideoDecodeFormats.values.last.code))!; - print(currentDecodeFormats.description); try { // 当前视频没有对应格式返回第一个 bool flag = false; @@ -287,6 +288,7 @@ class VideoDetailController extends GetxController defaultST = Duration(milliseconds: data.lastPlayTime!); if (autoPlay.value) { await playerInit(); + isShowCover.value = false; } } else { if (result['code'] == -404) { diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index c993fda9b..75eef2808 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -31,9 +31,10 @@ class VideoIntroPanel extends StatefulWidget { class _VideoIntroPanelState extends State with AutomaticKeepAliveClientMixin { - final VideoIntroController videoIntroController = - Get.put(VideoIntroController(), tag: Get.arguments['heroTag']); + late String heroTag; + late VideoIntroController videoIntroController; VideoDetailData? videoDetail; + late Future? _futureBuilderFuture; // 添加页面缓存 @override @@ -42,6 +43,11 @@ class _VideoIntroPanelState extends State @override void initState() { super.initState(); + + /// fix 全屏时参数丢失 + heroTag = Get.arguments['heroTag']; + videoIntroController = Get.put(VideoIntroController(), tag: heroTag); + _futureBuilderFuture = videoIntroController.queryVideoIntro(); videoIntroController.videoDetail.listen((value) { videoDetail = value; }); @@ -57,15 +63,20 @@ class _VideoIntroPanelState extends State Widget build(BuildContext context) { super.build(context); return FutureBuilder( - future: videoIntroController.queryVideoIntro(), + future: _futureBuilderFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SliverToBoxAdapter(child: SizedBox()); + } if (snapshot.data['status']) { // 请求成功 return Obx( () => VideoInfo( - loadingStatus: false, - videoDetail: videoIntroController.videoDetail.value), + loadingStatus: false, + videoDetail: videoIntroController.videoDetail.value, + heroTag: heroTag, + ), ); } else { // 请求错误 @@ -79,7 +90,11 @@ class _VideoIntroPanelState extends State ); } } else { - return VideoInfo(loadingStatus: true, videoDetail: videoDetail); + return VideoInfo( + loadingStatus: true, + videoDetail: videoDetail, + heroTag: heroTag, + ); } }, ); @@ -89,8 +104,10 @@ class _VideoIntroPanelState extends State class VideoInfo extends StatefulWidget { final bool loadingStatus; final VideoDetailData? videoDetail; + final String? heroTag; - const VideoInfo({Key? key, this.loadingStatus = false, this.videoDetail}) + const VideoInfo( + {Key? key, this.loadingStatus = false, this.videoDetail, this.heroTag}) : super(key: key); @override @@ -98,7 +115,8 @@ class VideoInfo extends StatefulWidget { } class _VideoInfoState extends State with TickerProviderStateMixin { - final String heroTag = Get.arguments['heroTag']; + // final String heroTag = Get.arguments['heroTag']; + late String heroTag; late final VideoIntroController videoIntroController; late final VideoDetailController videoDetailCtr; late final Map videoItem; @@ -117,7 +135,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { @override void initState() { super.initState(); - + heroTag = widget.heroTag!; videoIntroController = Get.put(VideoIntroController(), tag: heroTag); videoDetailCtr = Get.find(tag: heroTag); videoItem = videoIntroController.videoItem!; diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index d193de427..e144918f9 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -41,7 +41,6 @@ class _VideoDetailPageState extends State Get.put(VideoIntroController(), tag: Get.arguments['heroTag']); PlayerStatus playerStatus = PlayerStatus.playing; - // bool isShowCover = true; double doubleOffset = 0; Box localCache = GStrorage.localCache; @@ -49,11 +48,15 @@ class _VideoDetailPageState extends State late double statusBarHeight; final videoHeight = Get.size.width * 9 / 16; late Future _futureBuilderFuture; + // 自动退出全屏 + late bool autoExitFullcreen; @override void initState() { super.initState(); statusBarHeight = localCache.get('statusBarHeight'); + autoExitFullcreen = + setting.get(SettingBoxKey.enableAutoExit, defaultValue: false); videoSourceInit(); appbarStreamListen(); } @@ -63,7 +66,7 @@ class _VideoDetailPageState extends State _futureBuilderFuture = videoDetailController.queryVideoUrl(); if (videoDetailController.autoPlay.value) { plPlayerController = videoDetailController.plPlayerController; - playerListener(); + plPlayerController!.addStatusLister(playerListener); } } @@ -79,23 +82,15 @@ class _VideoDetailPageState extends State } // 播放器状态监听 - void playerListener() { - plPlayerController!.onPlayerStatusChanged.listen( - (PlayerStatus status) async { - playerStatus = status; - if (status == PlayerStatus.playing) { - videoDetailController.isShowCover.value = false; - } else { - // 播放完成停止 or 切换下一个 - if (status == PlayerStatus.completed) { - // 当只有1p或多p未打开自动播放时,播放完成还原进度条,展示控制栏 - plPlayerController!.seekTo(Duration.zero); - plPlayerController!.onLockControl(false); - plPlayerController!.videoPlayerController!.pause(); - } - } - }, - ); + void playerListener(PlayerStatus? status) { + if (status == PlayerStatus.completed) { + // 结束播放退出全屏 + if (autoExitFullcreen) { + plPlayerController!.triggerFullScreen(status: false); + } + // 播放完展示控制栏 + plPlayerController!.onLockControl(false); + } } // 继续播放或重新播放 @@ -110,11 +105,11 @@ class _VideoDetailPageState extends State plPlayerController = videoDetailController.plPlayerController; videoDetailController.autoPlay.value = true; videoDetailController.isShowCover.value = false; - playerListener(); } @override void dispose() { + plPlayerController!.removeStatusLister(playerListener); plPlayerController!.dispose(); super.dispose(); } @@ -128,6 +123,7 @@ class _VideoDetailPageState extends State } videoDetailController.defaultST = plPlayerController!.position.value; videoIntroController.isPaused = true; + plPlayerController!.removeStatusLister(playerListener); plPlayerController!.pause(); super.didPushNext(); } @@ -135,12 +131,14 @@ class _VideoDetailPageState extends State @override // 返回当前页面时 void didPopNext() async { + videoDetailController.isFirstTime = false; videoDetailController.playerInit(); videoIntroController.isPaused = false; if (_extendNestCtr.position.pixels == 0) { await Future.delayed(const Duration(milliseconds: 300)); plPlayerController!.play(); } + plPlayerController!.addStatusLister(playerListener); super.didPopNext(); } diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 861c7898c..648c059a3 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -11,18 +11,15 @@ import 'package:hive/hive.dart'; import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:pilipala/http/video.dart'; -import 'package:pilipala/plugin/pl_player/models/data_source.dart'; +import 'package:pilipala/plugin/pl_player/index.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:screen_brightness/screen_brightness.dart'; import 'package:universal_platform/universal_platform.dart'; // import 'package:wakelock_plus/wakelock_plus.dart'; -import 'models/data_status.dart'; -import 'models/play_speed.dart'; -import 'models/play_status.dart'; - Box videoStorage = GStrorage.video; +Box setting = GStrorage.setting; class PlPlayerController { Player? _videoPlayerController; @@ -84,6 +81,7 @@ class PlPlayerController { int _cid = 0; int _heartDuration = 0; bool _enableHeart = true; + bool _isFirstTime = true; Timer? _timer; Timer? _timerForSeek; @@ -248,6 +246,8 @@ class PlPlayerController { int cid = 0, // 历史记录开关 bool enableHeart = true, + // 是否首次加载 + bool isFirstTime = true, }) async { try { _autoPlay = autoplay; @@ -261,6 +261,7 @@ class PlPlayerController { _bvid = bvid; _cid = cid; _enableHeart = enableHeart; + _isFirstTime = isFirstTime; if (_videoPlayerController != null && _videoPlayerController!.state.playing) { @@ -281,6 +282,12 @@ class PlPlayerController { if (!_listenersInitialized) { startListeners(); } + bool autoEnterFullcreen = + setting.get(SettingBoxKey.enableAutoEnter, defaultValue: false); + if (autoEnterFullcreen && _isFirstTime) { + await Future.delayed(const Duration(milliseconds: 100)); + triggerFullScreen(); + } } catch (err) { dataStatus.status.value = DataStatus.error; print('plPlayer err: $err'); @@ -397,6 +404,8 @@ class PlPlayerController { } List subscriptions = []; + final List _positionListeners = []; + final List _statusListeners = []; /// 播放事件监听 void startListeners() { @@ -408,11 +417,21 @@ class PlPlayerController { } else { // playerStatus.status.value = PlayerStatus.paused; } + + /// 触发回调事件 + for (var element in _statusListeners) { + element(event ? PlayerStatus.playing : PlayerStatus.paused); + } makeHeartBeat(_position.value.inSeconds, type: 'status'); }), videoPlayerController!.stream.completed.listen((event) { if (event) { playerStatus.status.value = PlayerStatus.completed; + + /// 触发回调事件 + for (var element in _statusListeners) { + element(PlayerStatus.completed); + } } else { // playerStatus.status.value = PlayerStatus.playing; } @@ -423,6 +442,11 @@ class PlPlayerController { if (!isSliderMoving.value) { _sliderPosition.value = event; } + + /// 触发回调事件 + for (var element in _positionListeners) { + element(); + } makeHeartBeat(event.inSeconds); }), videoPlayerController!.stream.duration.listen((event) { @@ -714,6 +738,78 @@ class PlPlayerController { _isFullScreen.value = val; } + // 全屏 + Future triggerFullScreen({bool status = true}) async { + FullScreenMode mode = FullScreenModeCode.fromCode( + setting.get(SettingBoxKey.fullScreenMode, defaultValue: 0))!; + + if (!isFullScreen.value && status) { + /// 按照视频宽高比决定全屏方向 + switch (mode) { + case FullScreenMode.auto: + if (direction.value == 'horizontal') { + /// 进入全屏 + await enterFullScreen(); + // 横屏 + await landScape(); + } else { + // 竖屏 + await verticalScreen(); + } + break; + case FullScreenMode.vertical: + + /// 进入全屏 + await enterFullScreen(); + // 横屏 + await verticalScreen(); + break; + case FullScreenMode.horizontal: + + /// 进入全屏 + await enterFullScreen(); + // 横屏 + await landScape(); + break; + } + + toggleFullScreen(true); + var result = await showDialog( + context: Get.context!, + useSafeArea: false, + builder: (context) => Dialog.fullscreen( + backgroundColor: Colors.black, + child: PLVideoPlayer( + controller: this, + headerControl: headerControl, + ), + ), + ); + if (result == null) { + // 退出全屏 + exitFullScreen(); + await verticalScreen(); + toggleFullScreen(false); + } + } else if (isFullScreen.value) { + Get.back(); + exitFullScreen(); + await verticalScreen(); + toggleFullScreen(false); + } + } + + void addPositionListener(VoidCallback listener) => + _positionListeners.add(listener); + void removePositionListener(VoidCallback listener) => + _positionListeners.remove(listener); + void addStatusLister(Function(PlayerStatus status) listener) { + _statusListeners.add(listener); + } + + void removeStatusLister(Function(PlayerStatus status) listener) => + _statusListeners.remove(listener); + /// 截屏 Future screenshot() async { final Uint8List? screenshot = diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 077b5a1bb..ea7315dd8 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -159,67 +159,6 @@ class _PLVideoPlayerState extends State widget.controller.brightness.value = value; } - Future triggerFullScreen() async { - PlPlayerController _ = widget.controller; - mode = FullScreenModeCode.fromCode( - setting.get(SettingBoxKey.fullScreenMode, defaultValue: 0))!; - - if (!_.isFullScreen.value) { - /// 按照视频宽高比决定全屏方向 - switch (mode) { - case FullScreenMode.auto: - if (_.direction.value == 'horizontal') { - /// 进入全屏 - await enterFullScreen(); - // 横屏 - await landScape(); - } else { - // 竖屏 - await verticalScreen(); - } - break; - case FullScreenMode.vertical: - - /// 进入全屏 - await enterFullScreen(); - // 横屏 - await verticalScreen(); - break; - case FullScreenMode.horizontal: - - /// 进入全屏 - await enterFullScreen(); - // 横屏 - await landScape(); - break; - } - - _.toggleFullScreen(true); - var result = await showDialog( - context: Get.context!, - useSafeArea: false, - builder: (context) => Dialog.fullscreen( - backgroundColor: Colors.black, - child: PLVideoPlayer( - controller: _, - headerControl: _.headerControl, - ), - ), - ); - if (result == null) { - // 退出全屏 - exitFullScreen(); - await verticalScreen(); - _.toggleFullScreen(false); - } - } else { - Get.back(); - exitFullScreen(); - await verticalScreen(); - _.toggleFullScreen(false); - } - } - @override void dispose() { animationController.dispose(); @@ -559,13 +498,13 @@ class _PLVideoPlayerState extends State if (dy > _distance && dy > threshold) { if (_.isFullScreen.value) { // 下滑退出全屏 - await triggerFullScreen(); + await widget.controller.triggerFullScreen(status: false); } _distance = 0.0; } else if (dy < _distance && dy < -threshold) { if (!_.isFullScreen.value) { // 上滑进入全屏 - await triggerFullScreen(); + await widget.controller.triggerFullScreen(); } _distance = 0.0; } @@ -606,7 +545,7 @@ class _PLVideoPlayerState extends State position: 'bottom', child: BottomControl( controller: widget.controller, - triggerFullScreen: triggerFullScreen), + triggerFullScreen: widget.controller.triggerFullScreen), ), ), ], diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 21a8060b1..91094a396 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -97,6 +97,8 @@ class SettingBoxKey { static const String enableHA = 'enableHA'; static const String enableOnlineTotal = 'enableOnlineTotal'; static const String enableAutoBrightness = 'enableAutoBrightness'; + static const String enableAutoEnter = 'enableAutoEnter'; + static const String enableAutoExit = 'enableAutoExit'; /// 隐私 static const String blackMidsList = 'blackMidsList';