opt: live: send danmaku

Closes #618

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-04-06 11:22:19 +08:00
parent 216e3e606e
commit 754da4777a
5 changed files with 153 additions and 52 deletions

View File

@@ -4,6 +4,7 @@ import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/live/danmu_info.dart'; import 'package:PiliPlus/models/live/danmu_info.dart';
import 'package:PiliPlus/models/live/quality.dart'; import 'package:PiliPlus/models/live/quality.dart';
import 'package:PiliPlus/pages/mine/controller.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/services/service_locator.dart';
import 'package:PiliPlus/tcp/live.dart'; import 'package:PiliPlus/tcp/live.dart';
import 'package:PiliPlus/utils/danmaku.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:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/live.dart'; import 'package:PiliPlus/http/live.dart';
import 'package:PiliPlus/models/live/room_info.dart'; import 'package:PiliPlus/models/live/room_info.dart';
import 'package:PiliPlus/plugin/pl_player/index.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 '../../models/live/room_info_h5.dart';
import '../../utils/video_utils.dart'; import '../../utils/video_utils.dart';
@@ -42,6 +45,10 @@ class LiveRoomController extends GetxController {
late List<Map> acceptQnList = <Map>[]; late List<Map> acceptQnList = <Map>[];
RxString currentQnDesc = ''.obs; RxString currentQnDesc = ''.obs;
String? savedDanmaku;
late final isLogin = Accounts.main.isLogin;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@@ -241,4 +248,41 @@ class LiveRoomController extends GetxController {
.description; .description;
await queryLiveInfo(); 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,
);
},
),
);
}
} }

View File

@@ -44,7 +44,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
bool isPlay = true; bool isPlay = true;
Floating? floating; Floating? floating;
late final _isLogin = GStorage.userInfo.get('userInfoCache') != null;
late final _node = FocusNode(); late final _node = FocusNode();
late final _ctr = TextEditingController(); late final _ctr = TextEditingController();
StreamSubscription? _listener; StreamSubscription? _listener;
@@ -175,6 +174,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
headerControl: LiveHeaderControl( headerControl: LiveHeaderControl(
plPlayerController: plPlayerController, plPlayerController: plPlayerController,
floating: floating, floating: floating,
onSendDanmaku: _liveRoomController.onSendDanmaku,
), ),
bottomControl: BottomControl( bottomControl: BottomControl(
plPlayerController: plPlayerController, plPlayerController: plPlayerController,
@@ -601,18 +601,25 @@ class _LiveRoomPageState extends State<LiveRoomPage>
); );
void _onSendMsg(msg) async { void _onSendMsg(msg) async {
if (!_isLogin) { if (!_liveRoomController.isLogin) {
SmartDialog.showToast('未登录'); SmartDialog.showToast('未登录');
return; return;
} }
dynamic res = await LiveHttp.sendLiveMsg( dynamic res = await LiveHttp.sendLiveMsg(
roomId: _liveRoomController.roomId, msg: msg); roomId: _liveRoomController.roomId, msg: msg);
if (res['status']) { if (res['status']) {
_ctr.clear();
if (mounted) { if (mounted) {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
} }
SmartDialog.showToast('发送成功'); SmartDialog.showToast('发送成功');
plPlayerController.danmakuController?.addDanmaku(
DanmakuContentItem(
_ctr.text,
type: DanmakuItemType.scroll,
selfSend: true,
),
);
_ctr.clear();
} else { } else {
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
} }

View File

@@ -11,11 +11,13 @@ class LiveHeaderControl extends StatelessWidget implements PreferredSizeWidget {
const LiveHeaderControl({ const LiveHeaderControl({
required this.plPlayerController, required this.plPlayerController,
this.floating, this.floating,
required this.onSendDanmaku,
super.key, super.key,
}); });
final Floating? floating; final Floating? floating;
final PlPlayerController plPlayerController; final PlPlayerController plPlayerController;
final VoidCallback onSendDanmaku;
@override @override
Size get preferredSize => const Size(double.infinity, kToolbarHeight); Size get preferredSize => const Size(double.infinity, kToolbarHeight);
@@ -31,6 +33,22 @@ class LiveHeaderControl extends StatelessWidget implements PreferredSizeWidget {
title: Row( title: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ 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( Obx(
() => IconButton( () => IconButton(
onPressed: plPlayerController.setOnlyPlayAudio, onPressed: plPlayerController.setOnlyPlayAudio,

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:PiliPlus/common/widgets/icon_button.dart'; import 'package:PiliPlus/common/widgets/icon_button.dart';
import 'package:PiliPlus/http/danmaku.dart'; import 'package:PiliPlus/http/danmaku.dart';
import 'package:PiliPlus/http/live.dart';
import 'package:PiliPlus/main.dart'; import 'package:PiliPlus/main.dart';
import 'package:PiliPlus/pages/common/common_publish_page.dart'; import 'package:PiliPlus/pages/common/common_publish_page.dart';
import 'package:PiliPlus/pages/setting/slide_color_picker.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'; import 'package:get/get.dart';
class SendDanmakuPanel extends CommonPublishPage { class SendDanmakuPanel extends CommonPublishPage {
// video
final dynamic cid; final dynamic cid;
final dynamic bvid; final dynamic bvid;
final dynamic progress; final dynamic progress;
// live
final dynamic roomId;
final ValueChanged<DanmakuContentItem> callback; final ValueChanged<DanmakuContentItem> callback;
final bool darkVideoPage; final bool darkVideoPage;
@@ -23,9 +29,10 @@ class SendDanmakuPanel extends CommonPublishPage {
super.key, super.key,
super.initialValue, super.initialValue,
super.onSave, super.onSave,
required this.cid, this.cid,
required this.bvid, this.bvid,
required this.progress, this.progress,
this.roomId,
required this.callback, required this.callback,
required this.darkVideoPage, required this.darkVideoPage,
}); });
@@ -313,27 +320,28 @@ class _SendDanmakuPanelState extends CommonPublishPageState<SendDanmakuPanel> {
), ),
child: Row( child: Row(
children: [ children: [
Obx( if (widget.roomId == null)
() => iconButton( Obx(
context: context, () => iconButton(
tooltip: '弹幕样式', context: context,
onPressed: () { tooltip: '弹幕样式',
if (selectKeyboard.value) { onPressed: () {
selectKeyboard.value = false; if (selectKeyboard.value) {
updatePanelType(PanelType.emoji); selectKeyboard.value = false;
} else { updatePanelType(PanelType.emoji);
selectKeyboard.value = true; } else {
updatePanelType(PanelType.keyboard); selectKeyboard.value = true;
} updatePanelType(PanelType.keyboard);
}, }
bgColor: Colors.transparent, },
iconSize: 24, bgColor: Colors.transparent,
icon: Icons.text_format, iconSize: 24,
iconColor: selectKeyboard.value.not icon: Icons.text_format,
? themeData.colorScheme.primary iconColor: selectKeyboard.value.not
: themeData.colorScheme.onSurfaceVariant, ? themeData.colorScheme.primary
: themeData.colorScheme.onSurfaceVariant,
),
), ),
),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Form( child: Form(
@@ -441,33 +449,58 @@ class _SendDanmakuPanelState extends CommonPublishPageState<SendDanmakuPanel> {
@override @override
Future onCustomPublish({required String message, List? pictures}) async { Future onCustomPublish({required String message, List? pictures}) async {
SmartDialog.showLoading(msg: '发送中...'); SmartDialog.showLoading(msg: '发送中...');
final dynamic res = await DanmakuHttp.shootDanmaku( if (widget.roomId != null) {
oid: widget.cid, final res = await LiveHttp.sendLiveMsg(
bvid: widget.bvid, roomId: widget.roomId,
progress: widget.progress, msg: editController.text,
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 (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 { } 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']}');
}
} }
} }
} }

View File

@@ -1047,7 +1047,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
!plPlayerController.showControls.value; !plPlayerController.showControls.value;
}, },
onDoubleTapDown: (TapDownDetails details) { onDoubleTapDown: (TapDownDetails details) {
// live模式下禁用 锁定时🔒禁用
if (plPlayerController.controlsLock.value) { if (plPlayerController.controlsLock.value) {
return; return;
} }