From 754da4777a9db9c2f2cbf0ee4e1489467776f4d1 Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Sun, 6 Apr 2025 11:22:19 +0800 Subject: [PATCH] opt: live: send danmaku Closes #618 Signed-off-by: bggRGjQaUbCoE --- lib/pages/live_room/controller.dart | 44 ++++++ lib/pages/live_room/view.dart | 13 +- .../live_room/widgets/header_control.dart | 18 +++ .../detail/widgets/send_danmaku_panel.dart | 129 +++++++++++------- lib/plugin/pl_player/view.dart | 1 - 5 files changed, 153 insertions(+), 52 deletions(-) diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 4e0f95fd..174c136e 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/live/danmu_info.dart'; import 'package:PiliPlus/models/live/quality.dart'; import 'package:PiliPlus/pages/mine/controller.dart'; +import 'package:PiliPlus/pages/video/detail/widgets/send_danmaku_panel.dart'; import 'package:PiliPlus/services/service_locator.dart'; import 'package:PiliPlus/tcp/live.dart'; import 'package:PiliPlus/utils/danmaku.dart'; @@ -12,11 +13,13 @@ import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/live.dart'; import 'package:PiliPlus/models/live/room_info.dart'; import 'package:PiliPlus/plugin/pl_player/index.dart'; +import 'package:get/get_navigation/src/dialog/dialog_route.dart'; import '../../models/live/room_info_h5.dart'; import '../../utils/video_utils.dart'; @@ -42,6 +45,10 @@ class LiveRoomController extends GetxController { late List acceptQnList = []; RxString currentQnDesc = ''.obs; + String? savedDanmaku; + + late final isLogin = Accounts.main.isLogin; + @override void onInit() { super.onInit(); @@ -241,4 +248,41 @@ class LiveRoomController extends GetxController { .description; await queryLiveInfo(); } + + void onSendDanmaku() { + if (!isLogin) { + SmartDialog.showToast('未登录'); + return; + } + Navigator.of(Get.context!).push( + GetDialogRoute( + pageBuilder: (buildContext, animation, secondaryAnimation) { + return SendDanmakuPanel( + roomId: roomId, + initialValue: savedDanmaku, + onSave: (danmaku) => savedDanmaku = danmaku, + callback: (danmakuModel) { + savedDanmaku = null; + plPlayerController.danmakuController?.addDanmaku(danmakuModel); + }, + darkVideoPage: false, + ); + }, + transitionDuration: const Duration(milliseconds: 500), + transitionBuilder: (context, animation, secondaryAnimation, child) { + const begin = Offset(0.0, 1.0); + const end = Offset.zero; + const curve = Curves.linear; + + var tween = + Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); + + return SlideTransition( + position: animation.drive(tween), + child: child, + ); + }, + ), + ); + } } diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index a65f218b..d74f6ae9 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -44,7 +44,6 @@ class _LiveRoomPageState extends State bool isPlay = true; Floating? floating; - late final _isLogin = GStorage.userInfo.get('userInfoCache') != null; late final _node = FocusNode(); late final _ctr = TextEditingController(); StreamSubscription? _listener; @@ -175,6 +174,7 @@ class _LiveRoomPageState extends State headerControl: LiveHeaderControl( plPlayerController: plPlayerController, floating: floating, + onSendDanmaku: _liveRoomController.onSendDanmaku, ), bottomControl: BottomControl( plPlayerController: plPlayerController, @@ -601,18 +601,25 @@ class _LiveRoomPageState extends State ); void _onSendMsg(msg) async { - if (!_isLogin) { + if (!_liveRoomController.isLogin) { SmartDialog.showToast('未登录'); return; } dynamic res = await LiveHttp.sendLiveMsg( roomId: _liveRoomController.roomId, msg: msg); if (res['status']) { - _ctr.clear(); if (mounted) { FocusScope.of(context).unfocus(); } SmartDialog.showToast('发送成功'); + plPlayerController.danmakuController?.addDanmaku( + DanmakuContentItem( + _ctr.text, + type: DanmakuItemType.scroll, + selfSend: true, + ), + ); + _ctr.clear(); } else { SmartDialog.showToast(res['msg']); } diff --git a/lib/pages/live_room/widgets/header_control.dart b/lib/pages/live_room/widgets/header_control.dart index 27f69226..8158b5cb 100644 --- a/lib/pages/live_room/widgets/header_control.dart +++ b/lib/pages/live_room/widgets/header_control.dart @@ -11,11 +11,13 @@ class LiveHeaderControl extends StatelessWidget implements PreferredSizeWidget { const LiveHeaderControl({ required this.plPlayerController, this.floating, + required this.onSendDanmaku, super.key, }); final Floating? floating; final PlPlayerController plPlayerController; + final VoidCallback onSendDanmaku; @override Size get preferredSize => const Size(double.infinity, kToolbarHeight); @@ -31,6 +33,22 @@ class LiveHeaderControl extends StatelessWidget implements PreferredSizeWidget { title: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ + SizedBox( + width: 42, + height: 34, + child: IconButton( + tooltip: '发弹幕', + style: ButtonStyle( + padding: WidgetStateProperty.all(EdgeInsets.zero), + ), + onPressed: onSendDanmaku, + icon: const Icon( + Icons.comment_outlined, + size: 19, + color: Colors.white, + ), + ), + ), Obx( () => IconButton( onPressed: plPlayerController.setOnlyPlayAudio, diff --git a/lib/pages/video/detail/widgets/send_danmaku_panel.dart b/lib/pages/video/detail/widgets/send_danmaku_panel.dart index 647172ae..613aee0d 100644 --- a/lib/pages/video/detail/widgets/send_danmaku_panel.dart +++ b/lib/pages/video/detail/widgets/send_danmaku_panel.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:PiliPlus/common/widgets/icon_button.dart'; import 'package:PiliPlus/http/danmaku.dart'; +import 'package:PiliPlus/http/live.dart'; import 'package:PiliPlus/main.dart'; import 'package:PiliPlus/pages/common/common_publish_page.dart'; import 'package:PiliPlus/pages/setting/slide_color_picker.dart'; @@ -13,9 +14,14 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; class SendDanmakuPanel extends CommonPublishPage { + // video final dynamic cid; final dynamic bvid; final dynamic progress; + + // live + final dynamic roomId; + final ValueChanged callback; final bool darkVideoPage; @@ -23,9 +29,10 @@ class SendDanmakuPanel extends CommonPublishPage { super.key, super.initialValue, super.onSave, - required this.cid, - required this.bvid, - required this.progress, + this.cid, + this.bvid, + this.progress, + this.roomId, required this.callback, required this.darkVideoPage, }); @@ -313,27 +320,28 @@ class _SendDanmakuPanelState extends CommonPublishPageState { ), child: Row( children: [ - Obx( - () => iconButton( - context: context, - tooltip: '弹幕样式', - onPressed: () { - if (selectKeyboard.value) { - selectKeyboard.value = false; - updatePanelType(PanelType.emoji); - } else { - selectKeyboard.value = true; - updatePanelType(PanelType.keyboard); - } - }, - bgColor: Colors.transparent, - iconSize: 24, - icon: Icons.text_format, - iconColor: selectKeyboard.value.not - ? themeData.colorScheme.primary - : themeData.colorScheme.onSurfaceVariant, + if (widget.roomId == null) + Obx( + () => iconButton( + context: context, + tooltip: '弹幕样式', + onPressed: () { + if (selectKeyboard.value) { + selectKeyboard.value = false; + updatePanelType(PanelType.emoji); + } else { + selectKeyboard.value = true; + updatePanelType(PanelType.keyboard); + } + }, + bgColor: Colors.transparent, + iconSize: 24, + icon: Icons.text_format, + iconColor: selectKeyboard.value.not + ? themeData.colorScheme.primary + : themeData.colorScheme.onSurfaceVariant, + ), ), - ), const SizedBox(width: 12), Expanded( child: Form( @@ -441,33 +449,58 @@ class _SendDanmakuPanelState extends CommonPublishPageState { @override Future onCustomPublish({required String message, List? pictures}) async { SmartDialog.showLoading(msg: '发送中...'); - final dynamic res = await DanmakuHttp.shootDanmaku( - oid: widget.cid, - bvid: widget.bvid, - progress: widget.progress, - msg: editController.text, - mode: _mode.value, - fontsize: _fontsize.value, - color: _color.value.value & 0xFFFFFF, - ); - SmartDialog.dismiss(); - if (res['status']) { - Get.back(); - SmartDialog.showToast('发送成功'); - widget.callback( - DanmakuContentItem( - editController.text, - color: _color.value, - type: switch (_mode.value) { - 5 => DanmakuItemType.top, - 4 => DanmakuItemType.bottom, - _ => DanmakuItemType.scroll, - }, - selfSend: true, - ), + if (widget.roomId != null) { + final res = await LiveHttp.sendLiveMsg( + roomId: widget.roomId, + msg: editController.text, ); + if (res['status']) { + Get.back(); + SmartDialog.showToast('发送成功'); + widget.callback( + DanmakuContentItem( + editController.text, + color: _color.value, + type: switch (_mode.value) { + 5 => DanmakuItemType.top, + 4 => DanmakuItemType.bottom, + _ => DanmakuItemType.scroll, + }, + selfSend: true, + ), + ); + } else { + SmartDialog.showToast('发送失败: ${res['msg']}'); + } } else { - SmartDialog.showToast('发送失败: ${res['msg']}'); + final dynamic res = await DanmakuHttp.shootDanmaku( + oid: widget.cid, + bvid: widget.bvid, + progress: widget.progress, + msg: editController.text, + mode: _mode.value, + fontsize: _fontsize.value, + color: _color.value.value & 0xFFFFFF, + ); + SmartDialog.dismiss(); + if (res['status']) { + Get.back(); + SmartDialog.showToast('发送成功'); + widget.callback( + DanmakuContentItem( + editController.text, + color: _color.value, + type: switch (_mode.value) { + 5 => DanmakuItemType.top, + 4 => DanmakuItemType.bottom, + _ => DanmakuItemType.scroll, + }, + selfSend: true, + ), + ); + } else { + SmartDialog.showToast('发送失败: ${res['msg']}'); + } } } } diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 06b621b1..37b53d81 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -1047,7 +1047,6 @@ class _PLVideoPlayerState extends State !plPlayerController.showControls.value; }, onDoubleTapDown: (TapDownDetails details) { - // live模式下禁用 锁定时🔒禁用 if (plPlayerController.controlsLock.value) { return; }