feat: millisecond skip (#869)

* feat: millisecond skip

* fix: formatDuration

* fix: post segment
This commit is contained in:
My-Responsitories
2025-06-11 09:39:26 +08:00
committed by GitHub
parent bc2de4828b
commit 3655c31a48
6 changed files with 41 additions and 45 deletions

View File

@@ -10,11 +10,11 @@ class SegmentModel {
required this.segmentType, required this.segmentType,
required this.segment, required this.segment,
required this.skipType, required this.skipType,
this.hasSkipped, this.hasSkipped = false,
}); });
String UUID; String UUID;
SegmentType segmentType; SegmentType segmentType;
Pair<int, int> segment; Pair<int, int> segment;
SkipType skipType; SkipType skipType;
bool? hasSkipped; bool hasSkipped;
} }

View File

@@ -1,18 +1,18 @@
class SegmentItemModel { class SegmentItemModel {
String cid; String? cid;
String category; String category;
String actionType; String? actionType;
List<int> segment; List<int> segment;
String uuid; String uuid;
double videoDuration; num? videoDuration;
SegmentItemModel({ SegmentItemModel({
required this.cid, this.cid,
required this.category, required this.category,
required this.actionType, this.actionType,
required this.segment, required this.segment,
required this.uuid, required this.uuid,
required this.videoDuration, this.videoDuration,
}); });
factory SegmentItemModel.fromJson(Map<String, dynamic> json) => factory SegmentItemModel.fromJson(Map<String, dynamic> json) =>
@@ -20,9 +20,12 @@ class SegmentItemModel {
cid: json["cid"], cid: json["cid"],
category: json["category"], category: json["category"],
actionType: json["actionType"], actionType: json["actionType"],
segment: segment: (json["segment"] as List)
(json["segment"] as List).map((e) => (e as num).round()).toList(), .map((e) => ((e as num) * 1000).round())
.toList(),
uuid: json["UUID"], uuid: json["UUID"],
videoDuration: (json["videoDuration"] as num).toDouble(), videoDuration: json["videoDuration"] == null
? null
: (json["videoDuration"] as num) * 1000,
); );
} }

View File

@@ -611,7 +611,7 @@ class VideoDetailController extends GetxController
), ),
contentPadding: const EdgeInsets.only(left: 16, right: 8), contentPadding: const EdgeInsets.only(left: 16, right: 8),
subtitle: Text( subtitle: Text(
'${Utils.formatDuration(item.segment.first)}${Utils.formatDuration(item.segment.second)}', '${Utils.formatDuration(item.segment.first / 1000)}${Utils.formatDuration(item.segment.second / 1000)}',
style: const TextStyle(fontSize: 13), style: const TextStyle(fontSize: 13),
), ),
trailing: Row( trailing: Row(
@@ -696,7 +696,8 @@ class VideoDetailController extends GetxController
void handleSBData(List<SegmentItemModel> list) { void handleSBData(List<SegmentItemModel> list) {
if (list.isNotEmpty) { if (list.isNotEmpty) {
try { try {
double duration = list.first.videoDuration; final duration = list.first.videoDuration ??
plPlayerController.duration.value.inMilliseconds;
// segmentList // segmentList
segmentList.addAll(list segmentList.addAll(list
.where((item) => .where((item) =>
@@ -704,13 +705,12 @@ class VideoDetailController extends GetxController
item.segment[1] >= item.segment[0]) item.segment[1] >= item.segment[0])
.map( .map(
(item) { (item) {
SegmentType segmentType = SegmentType final segmentType = SegmentType.values.byName(item.category);
.values[plPlayerController.segmentTypes.indexOf(item.category)];
if (item.segment[0] == 0 && item.segment[1] == 0) { if (item.segment[0] == 0 && item.segment[1] == 0) {
videoLabel.value += videoLabel.value +=
'${videoLabel.value.isNotEmpty ? '/' : ''}${segmentType.title}'; '${videoLabel.value.isNotEmpty ? '/' : ''}${segmentType.title}';
} }
SkipType skipType = var skipType =
plPlayerController.blockSettings[segmentType.index].second; plPlayerController.blockSettings[segmentType.index].second;
if (skipType != SkipType.showOnly) { if (skipType != SkipType.showOnly) {
if (item.segment[1] == item.segment[0] || if (item.segment[1] == item.segment[0] ||
@@ -733,7 +733,7 @@ class VideoDetailController extends GetxController
if (positionSubscription == null && if (positionSubscription == null &&
!isShowCover.value && !isShowCover.value &&
plPlayerController.videoPlayerController != null) { plPlayerController.videoPlayerController != null) {
final currPost = plPlayerController.position.value.inSeconds; final currPost = plPlayerController.position.value.inMilliseconds;
if (currPost > segmentModel.segment.first && if (currPost > segmentModel.segment.first &&
currPost < segmentModel.segment.second) { currPost < segmentModel.segment.second) {
if (segmentModel.skipType == SkipType.alwaysSkip) { if (segmentModel.skipType == SkipType.alwaysSkip) {
@@ -760,8 +760,7 @@ class VideoDetailController extends GetxController
).toList()); ).toList());
// _segmentProgressList // _segmentProgressList
segmentProgressList ??= <Segment>[]; (segmentProgressList ??= <Segment>[]).addAll(segmentList.map((e) {
segmentProgressList!.addAll(segmentList.map((e) {
double start = (e.segment.first / duration).clamp(0.0, 1.0); double start = (e.segment.first / duration).clamp(0.0, 1.0);
double end = (e.segment.second / duration).clamp(0.0, 1.0); double end = (e.segment.second / duration).clamp(0.0, 1.0);
return Segment(start, end, _getColor(e.segmentType)); return Segment(start, end, _getColor(e.segmentType));
@@ -790,16 +789,18 @@ class VideoDetailController extends GetxController
int currentPos = position.inSeconds; int currentPos = position.inSeconds;
if (currentPos != _lastPos) { if (currentPos != _lastPos) {
_lastPos = currentPos; _lastPos = currentPos;
final msPos = currentPos * 1000;
for (SegmentModel item in segmentList) { for (SegmentModel item in segmentList) {
// if (kDebugMode) { // if (kDebugMode) {
// 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 (item.segment.first == position.inSeconds) { if (msPos <= item.segment.first &&
item.segment.first <= msPos + 1000) {
if (item.skipType == SkipType.alwaysSkip) { if (item.skipType == SkipType.alwaysSkip) {
onSkip(item); onSkip(item);
} else if (item.skipType == SkipType.skipOnce && } else if (item.skipType == SkipType.skipOnce &&
item.hasSkipped != true) { item.hasSkipped) {
item.hasSkipped = true; item.hasSkipped = true;
onSkip(item); onSkip(item);
} else if (item.skipType == SkipType.skipManually) { } else if (item.skipType == SkipType.skipManually) {
@@ -905,7 +906,7 @@ class VideoDetailController extends GetxController
try { try {
plPlayerController.danmakuController?.clear(); plPlayerController.danmakuController?.clear();
await plPlayerController.videoPlayerController await plPlayerController.videoPlayerController
?.seek(Duration(seconds: item.segment.second)); ?.seek(Duration(milliseconds: item.segment.second));
if (isSkip) { if (isSkip) {
if (GStorage.blockToast) { if (GStorage.blockToast) {
_showBlockToast('已跳过${item.segmentType.shortTitle}片段'); _showBlockToast('已跳过${item.segmentType.shortTitle}片段');

View File

@@ -325,8 +325,8 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
int start = max( int start = max(
0, 0,
(list![index].segment.first * 1000) (list![index].segment.first * 1000)
.toInt() - .round() -
2, 2000,
); );
await widget await widget
.plPlayerController.videoPlayerController! .plPlayerController.videoPlayerController!
@@ -349,7 +349,7 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
Duration( Duration(
milliseconds: milliseconds:
(list![index].segment.second * 1000) (list![index].segment.second * 1000)
.toInt(), .round(),
), ),
); );
} }

View File

@@ -300,12 +300,10 @@ class PlPlayerController {
late final List<Pair<SegmentType, SkipType>> blockSettings = late final List<Pair<SegmentType, SkipType>> blockSettings =
GStorage.blockSettings; GStorage.blockSettings;
late final List<Color> blockColor = GStorage.blockColor; late final List<Color> blockColor = GStorage.blockColor;
late final List<String> segmentTypes = late final Set<String> enableList = blockSettings
SegmentType.values.map((item) => item.name).toList();
late final List<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)
.toList(); .toSet();
late final blockServer = GStorage.blockServer; late final blockServer = GStorage.blockServer;
// settings // settings

View File

@@ -455,22 +455,16 @@ class Utils {
} }
static String formatDuration(num seconds) { static String formatDuration(num seconds) {
int hours = seconds ~/ 3600; int h = seconds ~/ 3600;
int minutes = (seconds % 3600) ~/ 60; seconds %= 3600;
num remainingSeconds = seconds % 60; int m = seconds ~/ 60;
if (remainingSeconds is double) { seconds %= 60;
remainingSeconds = remainingSeconds.toPrecision(3); String sms = seconds is double
} ? seconds.toStringAsFixed(3).padLeft(6, '0')
: seconds.toString().padLeft(2, '0');
String minutesStr = minutes.toString().padLeft(2, '0'); return h == 0
String secondsStr = remainingSeconds.toString().padLeft(2, '0'); ? "${m.toString().padLeft(2, '0')}:$sms"
: "${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:$sms";
if (hours > 0) {
String hoursStr = hours.toString().padLeft(2, '0');
return "$hoursStr:$minutesStr:$secondsStr";
} else {
return "$minutesStr:$secondsStr";
}
} }
static int duration(String duration) { static int duration(String duration) {