opt: merge danmaku in loop (#813)

This commit is contained in:
My-Responsitories
2025-05-05 00:38:05 +08:00
committed by GitHub
parent a49caa871d
commit 07d2b3b464
4 changed files with 54 additions and 55 deletions

View File

@@ -2,15 +2,15 @@ import 'package:PiliPlus/grpc/bilibili/community/service/dm/v1.pb.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart'; import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/api.dart';
import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
class DanmakuHttp { class DanmakuHttp {
// 获取视频弹幕 // 获取视频弹幕
static Future queryDanmaku({ static Future<LoadingState<DmSegMobileReply>> queryDanmaku({
required int cid, required int cid,
required int segmentIndex, required int segmentIndex,
required bool mergeDanmaku,
int queryCount = 1, int queryCount = 1,
}) async { }) async {
// 构建参数对象 // 构建参数对象
@@ -18,34 +18,18 @@ class DanmakuHttp {
await GrpcRepo.dmSegMobile(cid: cid, segmentIndex: segmentIndex); await GrpcRepo.dmSegMobile(cid: cid, segmentIndex: segmentIndex);
if (!response['status']) { if (!response['status']) {
if (queryCount >= 3) { if (queryCount >= 3) {
return {'status': false}; return const Error('');
} else { } else {
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
return await queryDanmaku( return await queryDanmaku(
cid: cid, cid: cid,
segmentIndex: segmentIndex, segmentIndex: segmentIndex,
mergeDanmaku: mergeDanmaku,
queryCount: ++queryCount, queryCount: ++queryCount,
); );
} }
} }
DmSegMobileReply data = response['data']; DmSegMobileReply data = response['data'];
if (mergeDanmaku && data.elems.isNotEmpty) { return LoadingState.success(data);
final Map counts = <String, int>{};
data.elems.retainWhere((item) {
int? count = counts[item.content];
counts[item.content] = count != null ? count + 1 : 1;
return count == null;
});
for (DanmakuElem item in data.elems) {
item.clearAttr();
final count = counts[item.content];
if (count != 1) {
item.attr = count;
}
}
}
return {'status': true, 'data': data};
} }
static Future shootDanmaku({ static Future shootDanmaku({

View File

@@ -6,15 +6,16 @@ class PlDanmakuController {
PlDanmakuController( PlDanmakuController(
this.cid, this.cid,
this.plPlayerController, this.plPlayerController,
); ) : mergeDanmaku = plPlayerController.mergeDanmaku;
final int cid; final int cid;
final PlPlayerController plPlayerController; final PlPlayerController plPlayerController;
final bool mergeDanmaku;
Map<int, List<DanmakuElem>> dmSegMap = {}; Map<int, List<DanmakuElem>> dmSegMap = {};
// 已请求的段落标记 // 已请求的段落标记
Map requestedSeg = {}; Set<int> requestedSeg = {};
static int segmentLength = 60 * 6 * 1000; static const int segmentLength = 60 * 6 * 1000;
void dispose() { void dispose() {
dmSegMap.clear(); dmSegMap.clear();
@@ -26,36 +27,48 @@ class PlDanmakuController {
} }
Future<void> queryDanmaku(int segmentIndex) async { Future<void> queryDanmaku(int segmentIndex) async {
if (requestedSeg[segmentIndex] == true) { if (requestedSeg.contains(segmentIndex)) {
return; return;
} }
requestedSeg[segmentIndex] = true; requestedSeg.add(segmentIndex);
final Map result = await DanmakuHttp.queryDanmaku( final result = await DanmakuHttp.queryDanmaku(
cid: cid, cid: cid,
segmentIndex: segmentIndex + 1, segmentIndex: segmentIndex + 1,
mergeDanmaku: plPlayerController.mergeDanmaku,
); );
if (result['status']) {
if (result['data'].elems.isNotEmpty) { if (result.isSuccess) {
for (DanmakuElem element in result['data'].elems) { final data = result.data;
if (element.mode == 7 && !plPlayerController.showSpecialDanmaku) { if (data.elems.isNotEmpty) {
continue; final Map<String, int> counts = {};
if (mergeDanmaku) {
data.elems.retainWhere((item) {
int? count = counts[item.content];
counts[item.content] = count != null ? count + 1 : 1;
return count == null;
});
}
for (final element in data.elems) {
if (mergeDanmaku) {
final count = counts[element.content];
if (count != 1) {
element.attr = count!;
} else {
element.clearAttr();
}
} }
int pos = element.progress ~/ 100; //每0.1秒存储一次 int pos = element.progress ~/ 100; //每0.1秒存储一次
if (dmSegMap[pos] == null) { (dmSegMap[pos] ??= []).add(element);
dmSegMap[pos] = [];
}
dmSegMap[pos]!.add(element);
} }
} }
} else { } else {
requestedSeg[segmentIndex] = false; requestedSeg.remove(segmentIndex);
} }
} }
List<DanmakuElem>? getCurrentDanmaku(int progress) { List<DanmakuElem>? getCurrentDanmaku(int progress) {
int segmentIndex = calcSegment(progress); int segmentIndex = calcSegment(progress);
if (requestedSeg[segmentIndex] != true) { if (!requestedSeg.contains(segmentIndex)) {
queryDanmaku(segmentIndex); queryDanmaku(segmentIndex);
return null; return null;
} }

View File

@@ -1264,13 +1264,13 @@ class HeaderControlState extends State<HeaderControl> {
/// 弹幕功能 /// 弹幕功能
void showSetDanmaku() { void showSetDanmaku() {
// 屏蔽类型 // 屏蔽类型
const List<Map<String, dynamic>> blockTypesList = [ const List<({int value, String label})> blockTypesList = [
{'value': 5, 'label': '顶部'}, (value: 5, label: '顶部'),
{'value': 2, 'label': '滚动'}, (value: 2, label: '滚动'),
{'value': 4, 'label': '底部'}, (value: 4, label: '底部'),
{'value': 6, 'label': '彩色'}, (value: 6, label: '彩色'),
]; ];
final List blockTypes = widget.controller.blockTypes; final blockTypes = widget.controller.blockTypes;
// 智能云屏蔽 // 智能云屏蔽
int danmakuWeight = widget.controller.danmakuWeight; int danmakuWeight = widget.controller.danmakuWeight;
// 显示区域 // 显示区域
@@ -1501,16 +1501,14 @@ class HeaderControlState extends State<HeaderControl> {
padding: const EdgeInsets.only(top: 12), padding: const EdgeInsets.only(top: 12),
child: Row( child: Row(
children: [ children: [
for (final Map<String, dynamic> i for (final (value: value, label: label)
in blockTypesList) ...[ in blockTypesList) ...[
ActionRowLineItem( ActionRowLineItem(
onTap: () { onTap: () {
final bool isChoose = if (blockTypes.contains(value)) {
blockTypes.contains(i['value']); blockTypes.remove(value);
if (isChoose) {
blockTypes.remove(i['value']);
} else { } else {
blockTypes.add(i['value']); blockTypes.add(value);
} }
widget.controller widget.controller
..blockTypes = blockTypes ..blockTypes = blockTypes
@@ -1527,8 +1525,8 @@ class HeaderControlState extends State<HeaderControl> {
); );
} catch (_) {} } catch (_) {}
}, },
text: i['label'], text: label,
selectStatus: blockTypes.contains(i['value']), selectStatus: blockTypes.contains(value),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
] ]

View File

@@ -249,7 +249,7 @@ class PlPlayerController {
bool showDanmaku = true; bool showDanmaku = true;
late final mergeDanmaku = GStorage.mergeDanmaku; late final mergeDanmaku = GStorage.mergeDanmaku;
// 弹幕相关配置 // 弹幕相关配置
late List blockTypes; late Set<int> blockTypes;
late double showArea; late double showArea;
late double opacity; late double opacity;
late double fontSize; late double fontSize;
@@ -434,7 +434,11 @@ class PlPlayerController {
setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: true); setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: true);
danmakuWeight = setting.get(SettingBoxKey.danmakuWeight, defaultValue: 0); danmakuWeight = setting.get(SettingBoxKey.danmakuWeight, defaultValue: 0);
filters = GStorage.danmakuFilterRule; filters = GStorage.danmakuFilterRule;
blockTypes = setting.get(SettingBoxKey.danmakuBlockType, defaultValue: []); blockTypes =
(setting.get(SettingBoxKey.danmakuBlockType, defaultValue: <int>[])
as Iterable)
.cast<int>()
.toSet();
showArea = setting.get(SettingBoxKey.danmakuShowArea, defaultValue: 0.5); showArea = setting.get(SettingBoxKey.danmakuShowArea, defaultValue: 0.5);
// 不透明度 // 不透明度
opacity = setting.get(SettingBoxKey.danmakuOpacity, defaultValue: 1.0); opacity = setting.get(SettingBoxKey.danmakuOpacity, defaultValue: 1.0);
@@ -1484,7 +1488,7 @@ class PlPlayerController {
void putDanmakuSettings() { void putDanmakuSettings() {
setting setting
..put(SettingBoxKey.danmakuWeight, danmakuWeight) ..put(SettingBoxKey.danmakuWeight, danmakuWeight)
..put(SettingBoxKey.danmakuBlockType, blockTypes) ..put(SettingBoxKey.danmakuBlockType, blockTypes.toList())
..put(SettingBoxKey.danmakuShowArea, showArea) ..put(SettingBoxKey.danmakuShowArea, showArea)
..put(SettingBoxKey.danmakuOpacity, opacity) ..put(SettingBoxKey.danmakuOpacity, opacity)
..put(SettingBoxKey.danmakuFontScale, fontSize) ..put(SettingBoxKey.danmakuFontScale, fontSize)