diff --git a/lib/pages/danmaku/view.dart b/lib/pages/danmaku/view.dart index 3e060112..481cc2d1 100644 --- a/lib/pages/danmaku/view.dart +++ b/lib/pages/danmaku/view.dart @@ -92,7 +92,7 @@ class _PlDanmakuState extends State { return; } - if (playerController.playerStatus.status.value != PlayerStatus.playing) { + if (!playerController.playerStatus.playing) { return; } diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index c61dd213..ca6b4d4e 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -165,7 +165,7 @@ class _MainAppState extends State void _onHideWindow() { if (_mainController.pauseOnMinimize) { _mainController.isPlaying = - PlPlayerController.instance?.playerStatus.status.value == + PlPlayerController.instance?.playerStatus.value == PlayerStatus.playing; PlPlayerController.pauseIfExists(); } diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index 84f1456e..27dc70f5 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -993,8 +993,7 @@ class VideoDetailController extends GetxController SmartDialog.showToast('UP主已关闭弹幕'); return; } - bool isPlaying = - plPlayerController.playerStatus.status.value == PlayerStatus.playing; + final isPlaying = plPlayerController.playerStatus.playing; if (isPlaying) { await plPlayerController.pause(); } @@ -1580,8 +1579,7 @@ class VideoDetailController extends GetxController void makeHeartBeat() { if (plPlayerController.enableHeart && - plPlayerController.playerStatus.status.value != - PlayerStatus.completed && + !plPlayerController.playerStatus.completed && playedTime != null) { try { plPlayerController.makeHeartBeat( diff --git a/lib/pages/video/reply_reply/view.dart b/lib/pages/video/reply_reply/view.dart index 19274a80..08e7abd5 100644 --- a/lib/pages/video/reply_reply/view.dart +++ b/lib/pages/video/reply_reply/view.dart @@ -2,12 +2,14 @@ import 'package:PiliPlus/common/skeleton/video_reply.dart'; import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; +import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo, Mode; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/common/slide/common_slide_page.dart'; import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart'; import 'package:PiliPlus/pages/video/reply_reply/controller.dart'; +import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; @@ -44,6 +46,51 @@ class VideoReplyReplyPanel extends CommonSlidePage { @override State createState() => _VideoReplyReplyPanelState(); + + static Future? toReply( + int oid, + int rootId, + String? rpIdStr, + int type, + Uri? uri, + ) { + final rpId = rpIdStr == null ? null : int.tryParse(rpIdStr); + return Get.to( + arguments: { + 'oid': oid, + 'rpid': rootId, + 'id': ?rpId, + 'type': type, + 'enterUri': ?uri, // save panel + }, + () => Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: const Text('评论详情'), + actions: [ + IconButton( + tooltip: '前往', + onPressed: uri == null + ? null + : () => PiliScheme.routePush(uri, businessId: type), + icon: const Icon(Icons.open_in_new), + ), + ], + ), + body: ViewSafeArea( + child: VideoReplyReplyPanel( + enableSlide: false, + oid: oid, + rpid: rootId, + isVideoDetail: false, + replyType: type, + firstFloor: null, + id: rpId, + ), + ), + ), + ); + } } class _VideoReplyReplyPanelState extends State diff --git a/lib/pages/video/view.dart b/lib/pages/video/view.dart index 2fd68637..b167d33d 100644 --- a/lib/pages/video/view.dart +++ b/lib/pages/video/view.dart @@ -379,7 +379,7 @@ class _VideoDetailPageVState extends State introController.canelTimer(); videoDetailController - ..playerStatus = plPlayerController?.playerStatus.status.value + ..playerStatus = plPlayerController?.playerStatus.value ..brightness = plPlayerController?.brightness.value; if (plPlayerController != null) { videoDetailController.makeHeartBeat(); @@ -406,8 +406,7 @@ class _VideoDetailPageVState extends State WidgetsBinding.instance.addObserver(this); plPlayerController?.isLive = false; - if (videoDetailController.plPlayerController.playerStatus.status.value == - PlayerStatus.playing && + if (videoDetailController.plPlayerController.playerStatus.playing && videoDetailController.playerStatus != PlayerStatus.playing) { videoDetailController.plPlayerController.pause(); } @@ -608,7 +607,7 @@ class _VideoDetailPageVState extends State videoDetailController.isCollapsing ? animHeight : videoDetailController.isCollapsing || - plPlayerController?.playerStatus.status.value == + plPlayerController?.playerStatus.value == PlayerStatus.playing ? videoDetailController.minVideoHeight : kToolbarHeight; @@ -724,7 +723,7 @@ class _VideoDetailPageVState extends State Text( '${videoDetailController.playedTime == null ? '立即' - : plPlayerController!.playerStatus.status.value == PlayerStatus.completed + : plPlayerController!.playerStatus.completed ? '重新' : '继续'}播放', style: TextStyle( diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 1255f2ff..fd4f6e07 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -69,7 +69,7 @@ class PlPlayerController { StreamSubscription? _playerEventSubs; /// [playerStatus] has a [status] observable - final PlPlayerStatus playerStatus = PlPlayerStatus(); + final playerStatus = PlPlayerStatus(PlayerStatus.playing); /// final PlPlayerDataStatus dataStatus = PlPlayerDataStatus(); @@ -154,7 +154,7 @@ class PlPlayerController { Stream get onDataStatusChanged => dataStatus.status.stream; /// 播放状态监听 - Stream get onPlayerStatusChanged => playerStatus.status.stream; + Stream get onPlayerStatusChanged => playerStatus.stream; /// 视频时长 Rx get duration => _duration; @@ -551,14 +551,14 @@ class PlPlayerController { // try to get PlayerStatus static PlayerStatus? getPlayerStatusIfExists() { - return _instance?.playerStatus.status.value; + return _instance?.playerStatus.value; } static Future pauseIfExists({ bool notify = true, bool isInterrupt = false, }) async { - if (_instance?.playerStatus.status.value == PlayerStatus.playing) { + if (_instance?.playerStatus.value == PlayerStatus.playing) { await _instance?.pause(notify: notify, isInterrupt: isInterrupt); } } @@ -1046,13 +1046,13 @@ class PlPlayerController { disableAutoEnterPip(); } } - playerStatus.status.value = PlayerStatus.playing; + playerStatus.value = PlayerStatus.playing; } else { disableAutoEnterPip(); - playerStatus.status.value = PlayerStatus.paused; + playerStatus.value = PlayerStatus.paused; } videoPlayerServiceHandler?.onStatusChange( - playerStatus.status.value, + playerStatus.value, isBuffering.value, isLive, ); @@ -1067,14 +1067,14 @@ class PlPlayerController { }), videoPlayerController!.stream.completed.listen((event) { if (event) { - playerStatus.status.value = PlayerStatus.completed; + playerStatus.value = PlayerStatus.completed; /// 触发回调事件 for (var element in _statusListeners) { element(PlayerStatus.completed); } } else { - // playerStatus.status.value = PlayerStatus.playing; + // playerStatus.value = PlayerStatus.playing; } makeHeartBeat(positionSeconds.value, type: HeartBeatType.completed); }), @@ -1102,7 +1102,7 @@ class PlPlayerController { videoPlayerController!.stream.buffering.listen((bool event) { isBuffering.value = event; videoPlayerServiceHandler?.onStatusChange( - playerStatus.status.value, + playerStatus.value, event, isLive, ); @@ -1237,7 +1237,7 @@ class PlPlayerController { } catch (e) { if (kDebugMode) debugPrint('seek failed: $e'); } - // if (playerStatus.status.value == PlayerStatus.paused) { + // if (playerStatus.value == PlayerStatus.paused) { // play(); // } t.cancel(); @@ -1294,14 +1294,14 @@ class PlPlayerController { audioSessionHandler?.setActive(true); - playerStatus.status.value = PlayerStatus.playing; + playerStatus.value = PlayerStatus.playing; // screenManager.setOverlays(false); } /// 暂停播放 Future pause({bool notify = true, bool isInterrupt = false}) async { await _videoPlayerController?.pause(); - playerStatus.status.value = PlayerStatus.paused; + playerStatus.value = PlayerStatus.paused; // 主动暂停时让出音频焦点 if (!isInterrupt) { @@ -1465,7 +1465,7 @@ class PlPlayerController { return; } if (val) { - if (playerStatus.status.value == PlayerStatus.playing) { + if (playerStatus.value == PlayerStatus.playing) { _longPressStatus.value = val; HapticFeedback.lightImpact(); await setPlaybackSpeed( @@ -1655,13 +1655,13 @@ class PlPlayerController { } if (!enableHeart || MineController.anonymity.value || progress == 0) { return; - } else if (playerStatus.status.value == PlayerStatus.paused) { + } else if (playerStatus.value == PlayerStatus.paused) { if (!isManual) { return; } } bool isComplete = - playerStatus.status.value == PlayerStatus.completed || + playerStatus.value == PlayerStatus.completed || type == HeartBeatType.completed; if ((durationSeconds.value - position.value).inMilliseconds > 1000) { isComplete = false; @@ -1761,7 +1761,7 @@ class PlPlayerController { // _showControls.close(); // _controlsLock.close(); - // playerStatus.status.close(); + // playerStatus.close(); // dataStatus.status.close(); await removeListeners(); diff --git a/lib/plugin/pl_player/models/play_status.dart b/lib/plugin/pl_player/models/play_status.dart index 2af09a61..ddd7f3e3 100644 --- a/lib/plugin/pl_player/models/play_status.dart +++ b/lib/plugin/pl_player/models/play_status.dart @@ -2,18 +2,18 @@ import 'package:get/get.dart'; enum PlayerStatus { completed, playing, paused } -class PlPlayerStatus { - Rx status = Rx(PlayerStatus.paused); +typedef PlPlayerStatus = Rx; +extension PlPlayerStatusExt on PlPlayerStatus { bool get playing { - return status.value == PlayerStatus.playing; + return value == PlayerStatus.playing; } bool get paused { - return status.value == PlayerStatus.paused; + return value == PlayerStatus.paused; } bool get completed { - return status.value == PlayerStatus.completed; + return value == PlayerStatus.completed; } } diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 80f4ff56..65535325 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -38,6 +38,7 @@ import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart'; import 'package:PiliPlus/plugin/pl_player/models/double_tap_type.dart'; import 'package:PiliPlus/plugin/pl_player/models/fullscreen_mode.dart'; import 'package:PiliPlus/plugin/pl_player/models/gesture_type.dart'; +import 'package:PiliPlus/plugin/pl_player/models/play_status.dart'; import 'package:PiliPlus/plugin/pl_player/models/video_fit_type.dart'; import 'package:PiliPlus/plugin/pl_player/widgets/app_bar_ani.dart'; import 'package:PiliPlus/plugin/pl_player/widgets/backward_seek.dart'; diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index 01a53189..284c8f19 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -23,7 +23,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -abstract class PiliScheme { +abstract final class PiliScheme { static late AppLinks appLinks; static StreamSubscription? listener; static final uriDigitRegExp = RegExp(r'/(\d+)'); @@ -123,50 +123,13 @@ abstract class PiliScheme { // to video reply String? oid = uriDigitRegExp.firstMatch(path)?.group(1); int? rpid = int.tryParse(queryParameters['comment_root_id']!); - String? commentSecondaryId = - queryParameters['comment_secondary_id']; if (oid != null && rpid != null) { - Get.to( - arguments: { - 'oid': oid, - 'rpid': rpid, - 'type': 1, - 'id': commentSecondaryId, - }, - () => Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: const Text('评论详情'), - actions: [ - IconButton( - tooltip: '前往原视频', - onPressed: () { - routePush( - Uri( - scheme: uri.scheme, - host: uri.host, - path: uri.path, - ), - ); - }, - icon: const Icon(Icons.open_in_new), - ), - ], - ), - body: ViewSafeArea( - child: VideoReplyReplyPanel( - enableSlide: false, - oid: int.parse(oid), - rpid: rpid, - isVideoDetail: false, - replyType: 1, - firstFloor: null, - id: commentSecondaryId != null - ? int.tryParse(commentSecondaryId) - : null, - ), - ), - ), + VideoReplyReplyPanel.toReply( + int.parse(oid), + rpid, + queryParameters['comment_secondary_id'], + 1, + uri.replace(query: ''), ); return true; } @@ -248,119 +211,36 @@ abstract class PiliScheme { } return false; case 'comment': - if (path.startsWith("/detail/")) { + if (path.startsWith("/detail/") || path.startsWith("/msg_fold/")) { // bilibili://comment/detail/17/832703053858603029/238686570016/?subType=0&anchor=238686628816&showEnter=1&extraIntentId=0&scene=1&enterName=%E6%9F%A5%E7%9C%8B%E5%8A%A8%E6%80%81%E8%AF%A6%E6%83%85&enterUri=bilibili://following/detail/832703053858603029 + // bilibili://comment/msg_fold/1/22222/33333/11111/?enterUri=bilibili://video/22222 //(aid) + // bilibili://comment/msg_fold/11/22222/33333/11111/?enterUri=bilibili://following/detail/44444 (dynId) final pathSegments = uri.pathSegments; final queryParameters = uri.queryParameters; final type = int.parse(pathSegments[1]); // business_id final oid = int.parse(pathSegments[2]); // subject_id final rootId = int.parse(pathSegments[3]); // root_id // target_id - final rpId = - queryParameters['anchor'] != - null // source_id - ? int.tryParse(queryParameters['anchor']!) - : null; // int subType = int.parse(queryParameters['subType'] ?? '0'); // int extraIntentId = // int.parse(queryParameters['extraIntentId'] ?? '0'); final enterUri = queryParameters['enterUri']; - Get.to( - arguments: { - 'oid': oid, - 'rpid': rootId, - 'id': rpId, - 'type': type, - 'enterUri': enterUri, - }, - () => Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: const Text('评论详情'), - actions: - enterUri != null || const [11, 16, 17].contains(type) - ? [ - IconButton( - tooltip: '前往', - onPressed: () { - if (enterUri != null) { - routePush(Uri.parse(enterUri)); - } else { - routePush( - Uri.parse( - 'bilibili://following/detail/$oid', - ), - ); - } - }, - icon: const Icon(Icons.open_in_new), - ), - ] - : null, - ), - body: ViewSafeArea( - child: VideoReplyReplyPanel( - enableSlide: false, - oid: oid, - rpid: rootId, - id: rpId, - isVideoDetail: false, - replyType: type, - firstFloor: null, - ), - ), - ), - ); - return true; - } else if (path.startsWith("/msg_fold/")) { - // bilibili://comment/msg_fold/1/22222/33333/11111/?enterUri=bilibili://video/22222 //(aid) - // bilibili://comment/msg_fold/11/22222/33333/11111/?enterUri=bilibili://following/detail/44444 (dynId) - List pathSegments = uri.pathSegments; - int type = int.parse(pathSegments[1]); // business_id - int oid = int.parse(pathSegments[2]); // subject_id - int rpId = int.parse(pathSegments[3]); // source_id - Get.to( - arguments: { - 'oid': oid, - 'rpid': rpId, - 'type': type, - }, - () => Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: const Text('评论详情'), - actions: [ - IconButton( - tooltip: '前往', - onPressed: () { - String? enterUri = uri.queryParameters['enterUri']; - if (enterUri != null) { - routePush(Uri.parse(enterUri), businessId: type); - } else { - routePush( - Uri.parse('bilibili://following/detail/$oid'), - businessId: type, - ); - } - }, - icon: const Icon(Icons.open_in_new), - ), - ], - ), - body: ViewSafeArea( - child: VideoReplyReplyPanel( - enableSlide: false, - oid: oid, - rpid: rpId, - isVideoDetail: false, - replyType: type, - firstFloor: null, - ), - ), - ), + VideoReplyReplyPanel.toReply( + oid, + rootId, + queryParameters['anchor'], // source_id + type, + enterUri != null + ? Uri.parse(enterUri) + : const [11, 16, 17].contains(type) + ? Uri( + scheme: 'bilibili', + host: 'following', + path: 'detail/$oid', + ) + : null, ); return true; } - return false; case 'following': // businessId == 17 => dynId == oid @@ -388,53 +268,19 @@ abstract class PiliScheme { if (commentRootId != null) { String? dynId = uriDigitRegExp.firstMatch(path)?.group(1); int? rpid = int.tryParse(commentRootId); - final commentSecondaryId = - queryParameters['comment_secondary_id']; if (dynId != null && rpid != null) { - Get.to( - arguments: { - 'oid': oid ?? dynId, - 'rpid': rpid, - 'type': businessId ?? 17, - 'id': commentSecondaryId, - }, - () => Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: const Text('评论详情'), - actions: [ - IconButton( - tooltip: '前往', - onPressed: () => _onPushDynDetail(uri, off), - icon: const Icon(Icons.open_in_new), - ), - ], - ), - body: ViewSafeArea( - child: VideoReplyReplyPanel( - enableSlide: false, - oid: oid ?? int.parse(dynId), - rpid: rpid, - isVideoDetail: false, - replyType: businessId ?? 17, - firstFloor: null, - id: commentSecondaryId != null - ? int.tryParse(commentSecondaryId) - : null, - ), - ), - ), + VideoReplyReplyPanel.toReply( + oid ?? int.parse(dynId), + rpid, + queryParameters['comment_secondary_id'], + businessId ?? 17, + uri.replace(query: ''), ); + return true; } - return true; - } else { - bool hasMatch = _onPushDynDetail(uri, off); - return hasMatch; } - } else { - bool hasMatch = _onPushDynDetail(uri, off); - return hasMatch; } + return _onPushDynDetail(uri, off); case 'album': String? rid = uriDigitRegExp.firstMatch(path)?.group(1); if (rid != null) { @@ -797,6 +643,20 @@ abstract class PiliScheme { final res = IdUtils.matchAvorBv(input: path); if (res.isNotEmpty) { final queryParameters = uri.queryParameters; + final rootIdStr = queryParameters['comment_root_id']; + if (rootIdStr != null) { + VideoReplyReplyPanel.toReply( + res.av ?? IdUtils.bv2av(res.bv!), + int.parse(rootIdStr), + queryParameters['comment_secondary_id'], + 1, + uri.replace( + queryParameters: Map.of(queryParameters) + ..remove('comment_root_id'), + ), + ); + return true; + } videoPush( res.av, res.bv, @@ -885,48 +745,16 @@ abstract class PiliScheme { case 'comment': // https://www.bilibili.com/h5/comment/sub?oid=123456&pageType=1&root=87654321 final queryParameters = uri.queryParameters; - String? oid = queryParameters['oid']; - String? root = queryParameters['root']; - String? pageType = queryParameters['pageType']; + final oid = queryParameters['oid']; + final root = queryParameters['root']; + final pageType = queryParameters['pageType']; if (oid != null && root != null && pageType != null) { - String? commentSecondaryId = queryParameters['comment_secondary_id']; - Get.to( - arguments: { - 'oid': oid, - 'rpid': root, - 'type': pageType, - 'id': commentSecondaryId, - }, - () => Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: const Text('评论详情'), - actions: pageType == '1' - ? [ - IconButton( - tooltip: '前往', - onPressed: () { - videoPush(int.parse(oid), null); - }, - icon: const Icon(Icons.open_in_new), - ), - ] - : null, - ), - body: ViewSafeArea( - child: VideoReplyReplyPanel( - enableSlide: false, - oid: int.parse(oid), - rpid: int.parse(root), - isVideoDetail: false, - replyType: int.parse(pageType), - firstFloor: null, - id: commentSecondaryId != null - ? int.tryParse(commentSecondaryId) - : null, - ), - ), - ), + VideoReplyReplyPanel.toReply( + int.parse(oid), + int.parse(root), + queryParameters['comment_secondary_id'], + int.parse(pageType), + Uri(scheme: 'bilibili', host: 'video', path: oid), ); return true; }