opt: segment (#1831)

* opt: segment

* revert: block config
This commit is contained in:
My-Responsitories
2026-02-10 16:13:38 +08:00
committed by GitHub
parent 0460030a2b
commit ed2bd069ee
6 changed files with 134 additions and 93 deletions

View File

@@ -1,19 +1,54 @@
// ignore_for_file: non_constant_identifier_names
import 'package:PiliPlus/common/widgets/pair.dart';
import 'package:PiliPlus/models/common/sponsor_block/segment_type.dart'; import 'package:PiliPlus/models/common/sponsor_block/segment_type.dart';
import 'package:PiliPlus/models/common/sponsor_block/skip_type.dart'; import 'package:PiliPlus/models/common/sponsor_block/skip_type.dart';
import 'package:PiliPlus/models_new/sponsor_block/segment_item.dart';
import 'package:PiliPlus/pages/sponsor_block/block_mixin.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
class SegmentModel { class SegmentModel implements Comparable<SegmentModel> {
SegmentModel({ SegmentModel({
required this.UUID, required this.uuid,
required this.segmentType, required this.segmentType,
required this.segment, required this.segment,
required this.skipType, required this.skipType,
}); });
String UUID; final String uuid;
SegmentType segmentType; final SegmentType segmentType;
Pair<int, int> segment; final (int, int) segment;
SkipType skipType; final SkipType skipType;
late bool hasSkipped = false; bool hasSkipped = false;
factory SegmentModel.fromItemModel(
SegmentItemModel model,
BlockConfigMixin? config,
) {
final segmentType = SegmentType.values.byName(model.category);
final segment = (model.segment[0], model.segment[1]);
SkipType skipType;
if (config != null) {
skipType = config.blockSettings[segmentType.index].second;
if (skipType != SkipType.showOnly) {
if (segment.isEq || segment.length < config.blockLimit) {
skipType = SkipType.showOnly;
}
}
} else {
skipType = Pref.pgcSkipType;
}
return SegmentModel(
uuid: model.uuid,
segmentType: segmentType,
segment: segment,
skipType: skipType,
);
}
@override
int compareTo(SegmentModel other) => segment.$1.compareTo(other.segment.$1);
}
extension IntRecordExt on (int, int) {
bool get isEq => $1 == $2;
int get length => $2 - $1;
bool contains(num other) => $1 <= other && other < $2;
} }

View File

@@ -1,6 +1,6 @@
import 'dart:async' show StreamSubscription, Timer; import 'dart:async' show StreamSubscription, Timer;
import 'dart:math' as math;
import 'package:PiliPlus/common/widgets/pair.dart';
import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart'; import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/sponsor_block.dart'; import 'package:PiliPlus/http/sponsor_block.dart';
@@ -21,15 +21,17 @@ import 'package:media_kit/media_kit.dart';
mixin BlockConfigMixin { mixin BlockConfigMixin {
late final pgcSkipType = Pref.pgcSkipType; late final pgcSkipType = Pref.pgcSkipType;
late final enablePgcSkip = pgcSkipType != SkipType.disable; late final enablePgcSkip = pgcSkipType != SkipType.disable;
late final bool enableSponsorBlock = Pref.enableSponsorBlock; late final enableSponsorBlock = Pref.enableSponsorBlock;
late final bool enableBlock = enableSponsorBlock || enablePgcSkip; late final enableBlock = enableSponsorBlock || enablePgcSkip;
late final double blockLimit = Pref.blockLimit; late final blockColor = Pref.blockColor;
late final blockLimit = Pref.blockLimit;
late final blockSettings = Pref.blockSettings; late final blockSettings = Pref.blockSettings;
late final List<Color> blockColor = Pref.blockColor; late final enableList = blockSettings
late final Set<String> enableList = blockSettings
.where((item) => item.second != SkipType.disable) .where((item) => item.second != SkipType.disable)
.map((item) => item.first.name) .map((item) => item.first.name)
.toSet(); .toSet();
Color _getColor(SegmentType segment) => blockColor[segment.index];
} }
mixin BlockMixin on GetxController { mixin BlockMixin on GetxController {
@@ -37,14 +39,12 @@ mixin BlockMixin on GetxController {
BlockConfigMixin get blockConfig; BlockConfigMixin get blockConfig;
StreamSubscription<Duration>? _blockListener; StreamSubscription<Duration>? _blockListener;
StreamSubscription<Duration>? get blockListener => _blockListener; StreamSubscription<Duration>? get blockListener => _blockListener;
Color _getBlockColor(SegmentType segment) =>
blockConfig.blockColor[segment.index];
late final List<SegmentModel> _segmentList = <SegmentModel>[]; late final List<SegmentModel> _segmentList = <SegmentModel>[];
late final RxList<Segment> segmentProgressList = <Segment>[].obs; late final RxList<Segment> segmentProgressList = <Segment>[].obs;
Timer? _skipTimer; Timer? _skipTimer;
late final listKey = GlobalKey<AnimatedListState>(); late final listKey = GlobalKey<AnimatedListState>();
late final List listData = []; late final List<Object> listData = [];
RxString? get videoLabel => null; RxString? get videoLabel => null;
Player? get player; Player? get player;
@@ -89,8 +89,7 @@ mixin BlockMixin on GetxController {
// debugPrint( // debugPrint(
// '${position.inSeconds},,${item.segment.first},,${item.segment.second},,${item.skipType.name},,${item.hasSkipped}'); // '${position.inSeconds},,${item.segment.first},,${item.segment.second},,${item.skipType.name},,${item.hasSkipped}');
// } // }
if (msPos <= item.segment.first && if (msPos <= item.segment.$1 && item.segment.$1 <= msPos + 1000) {
item.segment.first <= msPos + 1000) {
switch (item.skipType) { switch (item.skipType) {
case SkipType.alwaysSkip: case SkipType.alwaysSkip:
onSkip(item, isSeek: false); onSkip(item, isSeek: false);
@@ -118,7 +117,7 @@ mixin BlockMixin on GetxController {
Future<void> handleSBData(List<SegmentItemModel> list) async { Future<void> handleSBData(List<SegmentItemModel> list) async {
if (list.isNotEmpty) { if (list.isNotEmpty) {
try { try {
Future? future; Future<void>? future;
final duration = list.first.videoDuration ?? timeLength!; final duration = list.first.videoDuration ?? timeLength!;
// segmentList // segmentList
_segmentList.addAll( _segmentList.addAll(
@@ -130,41 +129,19 @@ mixin BlockMixin on GetxController {
) )
.map( .map(
(item) { (item) {
final segmentType = SegmentType.values.byName(item.category); final segmentModel = SegmentModel.fromItemModel(
if (item.segment[0] == 0 && item.segment[1] == 0) { item,
videoLabel?.value += isBlock ? blockConfig : null,
'${videoLabel!.value.isNotEmpty ? '/' : ''}${segmentType.title}';
}
SkipType skipType;
if (isBlock) {
skipType =
blockConfig.blockSettings[segmentType.index].second;
if (skipType != SkipType.showOnly) {
if (item.segment[1] == item.segment[0] ||
item.segment[1] - item.segment[0] <
blockConfig.blockLimit) {
skipType = SkipType.showOnly;
}
}
} else {
skipType = blockConfig.pgcSkipType;
}
final segmentModel = SegmentModel(
UUID: item.uuid,
segmentType: segmentType,
segment: Pair(
first: item.segment[0],
second: item.segment[1],
),
skipType: skipType,
); );
if (segmentModel.segment == const (0, 0)) {
videoLabel?.value +=
'${videoLabel!.value.isNotEmpty ? '/' : ''}${segmentModel.segmentType.title}';
}
if (_blockListener == null && autoPlay && player != null) { if (_blockListener == null && autoPlay && player != null) {
final currPos = currPosInMilliseconds; final currPos = currPosInMilliseconds;
if (currPos >= segmentModel.segment.first && if (segmentModel.segment.contains(currPos)) {
currPos < segmentModel.segment.second) {
_lastBlockPos = currPos; _lastBlockPos = currPos;
switch (segmentModel.skipType) { switch (segmentModel.skipType) {
@@ -182,7 +159,7 @@ mixin BlockMixin on GetxController {
return true; return true;
} }
return false; return false;
}); }, orElse: () => false);
} }
break; break;
case SkipType.skipManually: case SkipType.skipManually:
@@ -202,12 +179,12 @@ mixin BlockMixin on GetxController {
// _segmentProgressList // _segmentProgressList
segmentProgressList.addAll( segmentProgressList.addAll(
_segmentList.map((e) { _segmentList.map((e) {
double start = (e.segment.first / duration).clamp(0.0, 1.0); double start = (e.segment.$1 / duration).clamp(0.0, 1.0);
double end = (e.segment.second / duration).clamp(0.0, 1.0); double end = (e.segment.$2 / duration).clamp(0.0, 1.0);
return Segment( return Segment(
start: start, start: start,
end: end, end: end,
color: _getBlockColor(e.segmentType), color: blockConfig._getColor(e.segmentType),
); );
}), }),
); );
@@ -222,7 +199,7 @@ mixin BlockMixin on GetxController {
} }
} }
void onAddItem(dynamic item) { void onAddItem(Object item) {
if (listData.contains(item)) return; if (listData.contains(item)) return;
listData.insert(0, item); listData.insert(0, item);
listKey.currentState?.insertItem(0); listKey.currentState?.insertItem(0);
@@ -233,7 +210,7 @@ mixin BlockMixin on GetxController {
}); });
} }
void onRemoveItem(int index, item) { void onRemoveItem(int index, Object item) {
EasyThrottle.throttle( EasyThrottle.throttle(
'onRemoveItem', 'onRemoveItem',
const Duration(milliseconds: 500), const Duration(milliseconds: 500),
@@ -252,7 +229,7 @@ mixin BlockMixin on GetxController {
); );
} }
Widget buildItem(dynamic item, Animation<double> animation) => Widget buildItem(Object item, Animation<double> animation) =>
throw UnimplementedError(); throw UnimplementedError();
void _stopSkipTimer() { void _stopSkipTimer() {
@@ -264,6 +241,15 @@ mixin BlockMixin on GetxController {
Future<void>? seekTo(Duration duration, {required bool isSeek}); Future<void>? seekTo(Duration duration, {required bool isSeek});
void _skipToast(SegmentModel item) {
if (autoPlay && Pref.blockToast) {
_showBlockToast('已跳过${item.segmentType.shortTitle}片段');
}
if (isBlock && Pref.blockTrack) {
SponsorBlock.viewedVideoSponsorTime(item.uuid);
}
}
Future<void> onSkip( Future<void> onSkip(
SegmentModel item, { SegmentModel item, {
bool isSkip = true, bool isSkip = true,
@@ -271,16 +257,11 @@ mixin BlockMixin on GetxController {
}) async { }) async {
try { try {
await seekTo( await seekTo(
Duration(milliseconds: item.segment.second), Duration(milliseconds: item.segment.$2),
isSeek: isSeek, isSeek: isSeek,
); );
if (isSkip) { if (isSkip) {
if (autoPlay && Pref.blockToast) { _skipToast(item);
_showBlockToast('已跳过${item.segmentType.shortTitle}片段');
}
if (isBlock && Pref.blockTrack) {
SponsorBlock.viewedVideoSponsorTime(item.UUID);
}
} else { } else {
_showBlockToast('已跳至${item.segmentType.shortTitle}'); _showBlockToast('已跳至${item.segmentType.shortTitle}');
} }
@@ -316,7 +297,7 @@ mixin BlockMixin on GetxController {
title: const Text('赞成票', style: TextStyle(fontSize: 14)), title: const Text('赞成票', style: TextStyle(fontSize: 14)),
onTap: () { onTap: () {
Get.back(); Get.back();
_doVote(segment.UUID, 1); _doVote(segment.uuid, 1);
}, },
), ),
ListTile( ListTile(
@@ -324,7 +305,7 @@ mixin BlockMixin on GetxController {
title: const Text('反对票', style: TextStyle(fontSize: 14)), title: const Text('反对票', style: TextStyle(fontSize: 14)),
onTap: () { onTap: () {
Get.back(); Get.back();
_doVote(segment.UUID, 0); _doVote(segment.uuid, 0);
}, },
), ),
ListTile( ListTile(
@@ -363,7 +344,7 @@ mixin BlockMixin on GetxController {
onTap: () { onTap: () {
Get.back(); Get.back();
SponsorBlock.voteOnSponsorTime( SponsorBlock.voteOnSponsorTime(
uuid: segment.UUID, uuid: segment.uuid,
category: item, category: item,
).then((i) { ).then((i) {
SmartDialog.showToast( SmartDialog.showToast(
@@ -381,7 +362,7 @@ mixin BlockMixin on GetxController {
width: 10, width: 10,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: _getBlockColor(item), color: blockConfig._getColor(item),
), ),
), ),
style: const TextStyle(fontSize: 14, height: 1), style: const TextStyle(fontSize: 14, height: 1),
@@ -431,7 +412,7 @@ mixin BlockMixin on GetxController {
width: 10, width: 10,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: _getBlockColor(item.segmentType), color: blockConfig._getColor(item.segmentType),
), ),
), ),
style: const TextStyle(fontSize: 14, height: 1), style: const TextStyle(fontSize: 14, height: 1),
@@ -445,7 +426,7 @@ mixin BlockMixin on GetxController {
), ),
contentPadding: const EdgeInsets.only(left: 16, right: 8), contentPadding: const EdgeInsets.only(left: 16, right: 8),
subtitle: Text( subtitle: Text(
'${DurationUtils.formatDuration(item.segment.first / 1000)}${DurationUtils.formatDuration(item.segment.second / 1000)}', '${DurationUtils.formatDuration(item.segment.$1 / 1000)}${DurationUtils.formatDuration(item.segment.$2 / 1000)}',
style: const TextStyle(fontSize: 13), style: const TextStyle(fontSize: 13),
), ),
trailing: Row( trailing: Row(
@@ -455,7 +436,7 @@ mixin BlockMixin on GetxController {
item.skipType.label, item.skipType.label,
style: const TextStyle(fontSize: 13), style: const TextStyle(fontSize: 13),
), ),
if (item.segment.second != 0) if (item.segment.$2 != 0)
SizedBox( SizedBox(
width: 36, width: 36,
height: 36, height: 36,
@@ -514,6 +495,30 @@ mixin BlockMixin on GetxController {
segmentProgressList.clear(); segmentProgressList.clear();
} }
Duration? getFirstSegment([int pos = 0]) {
for (var i in _segmentList..sort()) {
final (start, end) = i.segment;
if (start == end) {
continue;
} else if (start - pos < 100) {
if (switch (i.skipType) {
.alwaysSkip => true,
.skipOnce => !i.hasSkipped,
_ => false,
}) {
_skipToast(i);
pos = math.max(pos, i.segment.$2);
}
} else {
break;
}
}
if (pos != 0) {
return Duration(milliseconds: pos);
}
return null;
}
@override @override
void onClose() { void onClose() {
_stopSkipTimer(); _stopSkipTimer();

View File

@@ -31,7 +31,7 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
final _url = 'https://github.com/hanydd/BilibiliSponsorBlock'; final _url = 'https://github.com/hanydd/BilibiliSponsorBlock';
final _textController = TextEditingController(); final _textController = TextEditingController();
double _blockLimit = Pref.blockLimit; double _blockLimit = Pref.blockLimit;
final List<Pair<SegmentType, SkipType>> _blockSettings = Pref.blockSettings; final _blockSettings = Pref.blockSettings;
final List<Color> _blockColor = Pref.blockColor; final List<Color> _blockColor = Pref.blockColor;
String _userId = Pref.blockUserID; String _userId = Pref.blockUserID;
bool _blockToast = Pref.blockToast; bool _blockToast = Pref.blockToast;

View File

@@ -508,7 +508,7 @@ class VideoDetailController extends GetxController
@override @override
Widget buildItem(dynamic item, Animation<double> animation) { Widget buildItem(Object item, Animation<double> animation) {
final theme = Get.theme; final theme = Get.theme;
return Align( return Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
@@ -536,7 +536,7 @@ class VideoDetailController extends GetxController
fontSize: 14, fontSize: 14,
text: item is SegmentModel text: item is SegmentModel
? '跳过: ${item.segmentType.shortTitle}' ? '跳过: ${item.segmentType.shortTitle}'
: '上次看到第${item + 1}P点击跳转', : '上次看到第${(item as int) + 1}P点击跳转',
onTap: (_) { onTap: (_) {
if (item is int) { if (item is int) {
try { try {
@@ -679,6 +679,10 @@ class VideoDetailController extends GetxController
Volume? volume, Volume? volume,
}) async { }) async {
final onlyPlayAudio = plPlayerController.onlyPlayAudio.value; final onlyPlayAudio = plPlayerController.onlyPlayAudio.value;
Duration? seek = seekToTime ?? defaultST ?? playedTime;
if (seek == null || seek == Duration.zero) {
seek = getFirstSegment();
}
await plPlayerController.setDataSource( await plPlayerController.setDataSource(
DataSource( DataSource(
videoSource: isFileSource videoSource: isFileSource
@@ -695,7 +699,7 @@ class VideoDetailController extends GetxController
'referer': HttpString.baseUrl, 'referer': HttpString.baseUrl,
}, },
), ),
seekTo: seekToTime ?? defaultST ?? playedTime, seekTo: seek,
duration: duration:
duration ?? duration ??
(data.timeLength == null (data.timeLength == null
@@ -803,16 +807,11 @@ class VideoDetailController extends GetxController
volume = data.volume; volume = data.volume;
final progress = args['progress']; final progress = args.remove('progress');
if (progress != null) { if (progress != null) {
this.defaultST = Duration(milliseconds: progress); this.defaultST = Duration(milliseconds: progress);
args['progress'] = null; } else if (defaultST == null && data.lastPlayTime != null) {
} else { this.defaultST = Duration(milliseconds: data.lastPlayTime!);
this.defaultST =
defaultST ??
(data.lastPlayTime == null
? Duration.zero
: Duration(milliseconds: data.lastPlayTime!));
} }
if (!isUgc && !fromReset && plPlayerController.enablePgcSkip) { if (!isUgc && !fromReset && plPlayerController.enablePgcSkip) {

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart';
@@ -473,14 +474,14 @@ class _PostPanelState extends State<PostPanel>
await videoCtr.play(); await videoCtr.play();
} }
final delay = start - seek; final delay = start - seek;
if (delay > 0) { Future<void> seekTo() => videoCtr.seek(
await Future.delayed(Duration(milliseconds: delay)); Duration(milliseconds: (item.segment.second * 1000).round()),
}
videoCtr.seek(
Duration(
milliseconds: (item.segment.second * 1000).round(),
),
); );
if (delay > 0) {
Timer(Duration(milliseconds: delay), seekTo);
} else {
seekTo();
}
} }
}, },
), ),

View File

@@ -39,6 +39,7 @@ import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:flex_seed_scheme/flex_seed_scheme.dart' show FlexSchemeVariant; import 'package:flex_seed_scheme/flex_seed_scheme.dart' show FlexSchemeVariant;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@@ -324,7 +325,7 @@ abstract final class Pref {
); );
static bool get blockTrack => static bool get blockTrack =>
_setting.get(SettingBoxKey.blockTrack, defaultValue: true); _setting.get(SettingBoxKey.blockTrack, defaultValue: !kDebugMode);
static bool get checkDynamic => static bool get checkDynamic =>
_setting.get(SettingBoxKey.checkDynamic, defaultValue: true); _setting.get(SettingBoxKey.checkDynamic, defaultValue: true);