feat: 直播弹幕发送

This commit is contained in:
guozhigq
2024-08-22 20:24:21 +08:00
parent b3e55be43c
commit f7ff6d7aa8
3 changed files with 142 additions and 75 deletions

View File

@ -84,4 +84,37 @@ class LiveHttp {
}; };
} }
} }
// 发送弹幕
static Future sendDanmaku({roomId, msg}) async {
var res = await Request().post(Api.sendLiveMsg, queryParameters: {
'bubble': 0,
'msg': msg,
'color': 16777215, // 颜色
'mode': 1, // 模式
'room_type': 0,
'jumpfrom': 71001, // 直播间来源
'reply_mid': 0,
'reply_attr': 0,
'replay_dmid': '',
'statistics': {"appId": 100, "platform": 5},
'fontsize': 25, // 字体大小
'rnd': DateTime.now().millisecondsSinceEpoch ~/ 1000, // 时间戳
'roomid': roomId,
'csrf': await Request.getCsrf(),
'csrf_token': await Request.getCsrf(),
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
} }

View File

@ -1,5 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:ui'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -40,6 +40,8 @@ class LiveRoomController extends GetxController {
// 弹幕消息列表 // 弹幕消息列表
RxList<LiveMessageModel> messageList = <LiveMessageModel>[].obs; RxList<LiveMessageModel> messageList = <LiveMessageModel>[].obs;
DanmakuController? danmakuController; DanmakuController? danmakuController;
// 输入控制器
TextEditingController inputController = TextEditingController();
@override @override
void onInit() { void onInit() {
@ -222,6 +224,23 @@ class LiveRoomController extends GetxController {
plSocket?.sendMessage(joinData); plSocket?.sendMessage(joinData);
} }
// 发送弹幕
void sendMsg() async {
final msg = inputController.text;
if (msg.isEmpty) {
return;
}
final res = await LiveHttp.sendDanmaku(
roomId: roomId,
msg: msg,
);
if (res['status']) {
inputController.clear();
} else {
SmartDialog.showToast(res['msg']);
}
}
@override @override
void onClose() { void onClose() {
plSocket?.onClose(); plSocket?.onClose();

View File

@ -97,7 +97,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
@override @override
void dispose() { void dispose() {
plPlayerController!.dispose(); plPlayerController.dispose();
if (floating != null) { if (floating != null) {
floating!.dispose(); floating!.dispose();
} }
@ -238,10 +238,10 @@ class _LiveRoomPageState extends State<LiveRoomPage>
), ),
), ),
PopScope( PopScope(
canPop: plPlayerController?.isFullScreen.value != true, canPop: plPlayerController.isFullScreen.value != true,
onPopInvoked: (bool didPop) { onPopInvoked: (bool didPop) {
if (plPlayerController?.isFullScreen.value == true) { if (plPlayerController.isFullScreen.value == true) {
plPlayerController!.triggerFullScreen(status: false); plPlayerController.triggerFullScreen(status: false);
} }
if (MediaQuery.of(context).orientation == if (MediaQuery.of(context).orientation ==
Orientation.landscape) { Orientation.landscape) {
@ -257,17 +257,53 @@ class _LiveRoomPageState extends State<LiveRoomPage>
child: videoPlayerPanel, child: videoPlayerPanel,
), ),
), ),
const SizedBox(height: 20),
// 显示消息的列表 // 显示消息的列表
buildMessageListUI( buildMessageListUI(
context, context,
_liveRoomController, _liveRoomController,
_scrollController, _scrollController,
), ),
// 底部安全距离 // 弹幕输入框
SizedBox( Container(
height: MediaQuery.of(context).padding.bottom + 20, padding: EdgeInsets.only(
) left: 14,
right: 14,
top: 4,
bottom: MediaQuery.of(context).padding.bottom + 20),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.5),
border: Border(
top: BorderSide(
color: Colors.white.withOpacity(0.1),
),
),
),
child: Row(
children: [
Expanded(
child: TextField(
controller: _liveRoomController.inputController,
style:
const TextStyle(color: Colors.white, fontSize: 13),
decoration: InputDecoration(
hintText: '发送弹幕',
hintStyle: TextStyle(
color: Colors.white.withOpacity(0.6),
),
border: InputBorder.none,
),
),
),
IconButton(
onPressed: () => _liveRoomController.sendMsg(),
icon: const Icon(
Icons.send,
color: Colors.white,
),
),
],
),
),
], ],
), ),
// 定位 快速滑动到底部 // 定位 快速滑动到底部
@ -324,15 +360,15 @@ Widget buildMessageListUI(
removeBottom: true, removeBottom: true,
child: ShaderMask( child: ShaderMask(
shaderCallback: (Rect bounds) { shaderCallback: (Rect bounds) {
return const LinearGradient( return LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: [
Colors.transparent, Colors.transparent,
Colors.black, Colors.black.withOpacity(0.5),
Colors.black, Colors.black,
], ],
stops: [0.0, 0.1, 1.0], stops: const [0.01, 0.05, 0.2],
).createShader(bounds); ).createShader(bounds);
}, },
blendMode: BlendMode.dstIn, blendMode: BlendMode.dstIn,
@ -342,35 +378,46 @@ Widget buildMessageListUI(
itemBuilder: (context, index) { itemBuilder: (context, index) {
final LiveMessageModel liveMsgItem = final LiveMessageModel liveMsgItem =
liveRoomController.messageList[index]; liveRoomController.messageList[index];
return Padding( return Align(
padding: EdgeInsets.only( alignment: Alignment.centerLeft,
top: index == 0 ? 40.0 : 4.0, child: Container(
bottom: 4.0, decoration: BoxDecoration(
left: 20.0, color: Colors.grey.withOpacity(0.1),
right: 20.0, borderRadius: const BorderRadius.all(Radius.circular(20)),
), ),
child: Text.rich( margin: EdgeInsets.only(
TextSpan( top: index == 0 ? 20.0 : 0.0,
style: const TextStyle(color: Colors.white), bottom: 6.0,
children: [ left: 14.0,
TextSpan( right: 14.0,
text: '${liveMsgItem.userName}: ', ),
style: TextStyle( padding: const EdgeInsets.symmetric(
color: Colors.white.withOpacity(0.6), vertical: 3.0,
horizontal: 10.0,
),
child: Text.rich(
TextSpan(
style: const TextStyle(color: Colors.white),
children: [
TextSpan(
text: '${liveMsgItem.userName}: ',
style: TextStyle(
color: Colors.white.withOpacity(0.6),
),
recognizer: TapGestureRecognizer()
..onTap = () {
// 处理点击事件
print('Text clicked');
},
), ),
recognizer: TapGestureRecognizer() TextSpan(
..onTap = () { children: [
// 处理点击事件 ...buildMessageTextSpan(context, liveMsgItem)
print('Text clicked'); ],
}, // text: liveMsgItem.message,
), ),
TextSpan( ],
children: [ ),
...buildMessageTextSpan(context, liveMsgItem)
],
// text: liveMsgItem.message,
),
],
), ),
), ),
); );
@ -392,23 +439,7 @@ List<InlineSpan> buildMessageTextSpan(
if (liveMsgItem.emots == null) { if (liveMsgItem.emots == null) {
// 没有表情包的消息 // 没有表情包的消息
inlineSpanList.add( inlineSpanList.add(
TextSpan( TextSpan(text: liveMsgItem.message ?? ''),
text: liveMsgItem.message ?? '',
style: const TextStyle(
shadows: [
Shadow(
offset: Offset(2.0, 2.0),
blurRadius: 3.0,
color: Colors.black,
),
Shadow(
offset: Offset(-1.0, -1.0),
blurRadius: 3.0,
color: Colors.black,
),
],
),
),
); );
} else { } else {
// 有表情包的消息 使用正则匹配 表情包用图片渲染 // 有表情包的消息 使用正则匹配 表情包用图片渲染
@ -435,23 +466,7 @@ List<InlineSpan> buildMessageTextSpan(
}, },
onNonMatch: (String nonMatch) { onNonMatch: (String nonMatch) {
inlineSpanList.add( inlineSpanList.add(
TextSpan( TextSpan(text: nonMatch),
text: nonMatch,
style: const TextStyle(
shadows: [
Shadow(
offset: Offset(2.0, 2.0),
blurRadius: 3.0,
color: Colors.black,
),
Shadow(
offset: Offset(-1.0, -1.0),
blurRadius: 3.0,
color: Colors.black,
),
],
),
),
); );
return nonMatch; return nonMatch;
}, },