feat: 直播弹幕发送
This commit is contained in:
@ -558,4 +558,7 @@ class Api {
|
|||||||
/// 直播间弹幕信息
|
/// 直播间弹幕信息
|
||||||
static const String getDanmuInfo =
|
static const String getDanmuInfo =
|
||||||
'${HttpString.liveBaseUrl}/xlive/web-room/v1/index/getDanmuInfo';
|
'${HttpString.liveBaseUrl}/xlive/web-room/v1/index/getDanmuInfo';
|
||||||
|
|
||||||
|
/// 直播间发送弹幕
|
||||||
|
static const String sendLiveMsg = '${HttpString.liveBaseUrl}/msg/send';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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,12 +378,22 @@ 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)),
|
||||||
|
),
|
||||||
|
margin: EdgeInsets.only(
|
||||||
|
top: index == 0 ? 20.0 : 0.0,
|
||||||
|
bottom: 6.0,
|
||||||
|
left: 14.0,
|
||||||
|
right: 14.0,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 3.0,
|
||||||
|
horizontal: 10.0,
|
||||||
),
|
),
|
||||||
child: Text.rich(
|
child: Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
@ -373,6 +419,7 @@ Widget buildMessageListUI(
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -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;
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user