添加基础发送弹幕功能
This commit is contained in:
@ -312,6 +312,10 @@ class Api {
|
|||||||
|
|
||||||
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
||||||
|
|
||||||
|
//发送视频弹幕
|
||||||
|
//https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/danmaku/action.md
|
||||||
|
static const String shootDanmaku = '/x/v2/dm/post';
|
||||||
|
|
||||||
// up主分组
|
// up主分组
|
||||||
static const String followUpTag = '/x/relation/tags';
|
static const String followUpTag = '/x/relation/tags';
|
||||||
|
|
||||||
|
@ -24,4 +24,72 @@ class DanmakaHttp {
|
|||||||
);
|
);
|
||||||
return DmSegMobileReply.fromBuffer(response.data);
|
return DmSegMobileReply.fromBuffer(response.data);
|
||||||
}
|
}
|
||||||
|
static Future shootDanmaku({
|
||||||
|
int type = 1,//弹幕类选择(1:视频弹幕 2:漫画弹幕)
|
||||||
|
required int oid,// 视频cid
|
||||||
|
required String msg,//弹幕文本(长度小于 100 字符)
|
||||||
|
int mode = 1,// 弹幕类型(1:滚动弹幕 4:底端弹幕 5:顶端弹幕 6:逆向弹幕(不能使用) 7:高级弹幕 8:代码弹幕(不能使用) 9:BAS弹幕(pool必须为2))
|
||||||
|
// String? aid,// 稿件avid
|
||||||
|
// String? bvid,// bvid与aid必须有一个
|
||||||
|
required String bvid,
|
||||||
|
int? progress,// 弹幕出现在视频内的时间(单位为毫秒,默认为0)
|
||||||
|
int? color,// 弹幕颜色(默认白色,16777215)
|
||||||
|
int? fontsize,// 弹幕字号(默认25)
|
||||||
|
int? pool,// 弹幕池选择(0:普通池 1:字幕池 2:特殊池(代码/BAS弹幕)默认普通池,0)
|
||||||
|
//int? rnd,// 当前时间戳*1000000(若无此项,则发送弹幕冷却时间限制为90s;若有此项,则发送弹幕冷却时间限制为5s)
|
||||||
|
int? colorful,//60001:专属渐变彩色(需要会员)
|
||||||
|
int? checkbox_type,//是否带 UP 身份标识(0:普通;4:带有标识)
|
||||||
|
// String? csrf,//CSRF Token(位于 Cookie) Cookie 方式必要
|
||||||
|
// String? access_key,// APP 登录 Token APP 方式必要
|
||||||
|
}) async {
|
||||||
|
// 构建参数对象
|
||||||
|
// assert(aid != null || bvid != null);
|
||||||
|
// assert(csrf != null || access_key != null);
|
||||||
|
assert(msg.length < 100);
|
||||||
|
// 构建参数对象
|
||||||
|
var params = <String, dynamic>{
|
||||||
|
'type': type,
|
||||||
|
'oid': oid,
|
||||||
|
'msg': msg,
|
||||||
|
'mode': mode,
|
||||||
|
//'aid': aid,
|
||||||
|
'bvid': bvid,
|
||||||
|
'progress': progress,
|
||||||
|
'color': color,
|
||||||
|
'fontsize': fontsize,
|
||||||
|
'pool': pool,
|
||||||
|
'rnd': DateTime.now().microsecondsSinceEpoch,
|
||||||
|
'colorful': colorful,
|
||||||
|
'checkbox_type': checkbox_type,
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
// 'access_key': access_key,
|
||||||
|
}..removeWhere((key, value) => value == null);
|
||||||
|
|
||||||
|
var response = await Request().post(
|
||||||
|
Api.shootDanmaku,
|
||||||
|
data: params,
|
||||||
|
options: Options(
|
||||||
|
contentType: Headers.formUrlEncodedContentType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': '弹幕发送失败,状态码:${response.statusCode}',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (response.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': response.data['data'],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': "${response.data['code']}: ${response.data['message']}",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
|
|||||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
import 'package:pilipala/http/danmaku.dart';
|
||||||
|
|
||||||
class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
|
class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
|
||||||
final PlPlayerController? controller;
|
final PlPlayerController? controller;
|
||||||
@ -179,6 +180,84 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 发送弹幕
|
||||||
|
void showShootDanmakuSheet() {
|
||||||
|
final TextEditingController textController = TextEditingController();
|
||||||
|
bool isSending = false; // 追踪是否正在发送
|
||||||
|
showDialog(
|
||||||
|
context: Get.context!,
|
||||||
|
builder: (context) {
|
||||||
|
// TODO: 支持更多类型和颜色的弹幕
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('发送弹幕(测试)'),
|
||||||
|
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
||||||
|
return TextField(
|
||||||
|
controller: textController,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
StatefulBuilder(builder: (context, StateSetter setState) {
|
||||||
|
return TextButton(
|
||||||
|
onPressed: isSending
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
String msg = textController.text;
|
||||||
|
if (msg.isEmpty) {
|
||||||
|
SmartDialog.showToast('弹幕内容不能为空');
|
||||||
|
return;
|
||||||
|
} else if (msg.length > 100) {
|
||||||
|
SmartDialog.showToast('弹幕内容不能超过100个字符');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
isSending = true; // 开始发送,更新状态
|
||||||
|
});
|
||||||
|
//修改按钮文字
|
||||||
|
// SmartDialog.showToast('弹幕发送中,\n$msg');
|
||||||
|
dynamic res = await DanmakaHttp.shootDanmaku(
|
||||||
|
oid: widget.videoDetailCtr!.cid!.value,
|
||||||
|
msg: textController.text,
|
||||||
|
bvid: widget.videoDetailCtr!.bvid!,
|
||||||
|
progress:
|
||||||
|
widget.controller!.position.value.inMilliseconds,
|
||||||
|
type: 1,
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
isSending = false; // 发送结束,更新状态
|
||||||
|
});
|
||||||
|
if (res['status']) {
|
||||||
|
SmartDialog.showToast('发送成功');
|
||||||
|
// 发送成功,自动预览该弹幕,避免重新请求
|
||||||
|
// TODO: 暂停状态下预览弹幕仍会移动与计时,可考虑添加到dmSegList或其他方式实现
|
||||||
|
widget.controller!.danmakuController!.addItems([
|
||||||
|
DanmakuItem(
|
||||||
|
msg,
|
||||||
|
color: Colors.white,
|
||||||
|
time: widget.controller!.position.value.inMilliseconds,
|
||||||
|
type: DanmakuItemType.scroll,
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
Get.back();
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast('发送失败,错误信息为${res['msg']}');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(isSending ? '发送中...' : '发送'),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// 选择倍速
|
/// 选择倍速
|
||||||
void showSetSpeedSheet() {
|
void showSetSpeedSheet() {
|
||||||
double currentSpeed = widget.controller!.playbackSpeed;
|
double currentSpeed = widget.controller!.playbackSpeed;
|
||||||
@ -825,6 +904,20 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
// ),
|
// ),
|
||||||
// fuc: () => _.screenshot(),
|
// fuc: () => _.screenshot(),
|
||||||
// ),
|
// ),
|
||||||
|
SizedBox(
|
||||||
|
width: 56,
|
||||||
|
height: 34,
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
onPressed: () => showShootDanmakuSheet(),
|
||||||
|
child: const Text(
|
||||||
|
'发弹幕',
|
||||||
|
style: textStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 34,
|
width: 34,
|
||||||
height: 34,
|
height: 34,
|
||||||
|
Reference in New Issue
Block a user