diff --git a/lib/models/common/sponsor_block/segment_model.dart b/lib/models/common/sponsor_block/segment_model.dart index 1468cc40e..496b9d99b 100644 --- a/lib/models/common/sponsor_block/segment_model.dart +++ b/lib/models/common/sponsor_block/segment_model.dart @@ -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/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({ - required this.UUID, + required this.uuid, required this.segmentType, required this.segment, required this.skipType, }); - String UUID; - SegmentType segmentType; - Pair segment; - SkipType skipType; - late bool hasSkipped = false; + final String uuid; + final SegmentType segmentType; + final (int, int) segment; + final SkipType skipType; + 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; } diff --git a/lib/pages/sponsor_block/block_mixin.dart b/lib/pages/sponsor_block/block_mixin.dart index b4337b974..907908545 100644 --- a/lib/pages/sponsor_block/block_mixin.dart +++ b/lib/pages/sponsor_block/block_mixin.dart @@ -1,6 +1,6 @@ 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/http/loading_state.dart'; import 'package:PiliPlus/http/sponsor_block.dart'; @@ -21,15 +21,17 @@ import 'package:media_kit/media_kit.dart'; mixin BlockConfigMixin { late final pgcSkipType = Pref.pgcSkipType; late final enablePgcSkip = pgcSkipType != SkipType.disable; - late final bool enableSponsorBlock = Pref.enableSponsorBlock; - late final bool enableBlock = enableSponsorBlock || enablePgcSkip; - late final double blockLimit = Pref.blockLimit; + late final enableSponsorBlock = Pref.enableSponsorBlock; + late final enableBlock = enableSponsorBlock || enablePgcSkip; + late final blockColor = Pref.blockColor; + late final blockLimit = Pref.blockLimit; late final blockSettings = Pref.blockSettings; - late final List blockColor = Pref.blockColor; - late final Set enableList = blockSettings + late final enableList = blockSettings .where((item) => item.second != SkipType.disable) .map((item) => item.first.name) .toSet(); + + Color _getColor(SegmentType segment) => blockColor[segment.index]; } mixin BlockMixin on GetxController { @@ -37,14 +39,12 @@ mixin BlockMixin on GetxController { BlockConfigMixin get blockConfig; StreamSubscription? _blockListener; StreamSubscription? get blockListener => _blockListener; - Color _getBlockColor(SegmentType segment) => - blockConfig.blockColor[segment.index]; late final List _segmentList = []; late final RxList segmentProgressList = [].obs; Timer? _skipTimer; late final listKey = GlobalKey(); - late final List listData = []; + late final List listData = []; RxString? get videoLabel => null; Player? get player; @@ -89,8 +89,7 @@ mixin BlockMixin on GetxController { // debugPrint( // '${position.inSeconds},,${item.segment.first},,${item.segment.second},,${item.skipType.name},,${item.hasSkipped}'); // } - if (msPos <= item.segment.first && - item.segment.first <= msPos + 1000) { + if (msPos <= item.segment.$1 && item.segment.$1 <= msPos + 1000) { switch (item.skipType) { case SkipType.alwaysSkip: onSkip(item, isSeek: false); @@ -118,7 +117,7 @@ mixin BlockMixin on GetxController { Future handleSBData(List list) async { if (list.isNotEmpty) { try { - Future? future; + Future? future; final duration = list.first.videoDuration ?? timeLength!; // segmentList _segmentList.addAll( @@ -130,41 +129,19 @@ mixin BlockMixin on GetxController { ) .map( (item) { - final segmentType = SegmentType.values.byName(item.category); - if (item.segment[0] == 0 && item.segment[1] == 0) { - videoLabel?.value += - '${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, + final segmentModel = SegmentModel.fromItemModel( + item, + isBlock ? blockConfig : null, ); + if (segmentModel.segment == const (0, 0)) { + videoLabel?.value += + '${videoLabel!.value.isNotEmpty ? '/' : ''}${segmentModel.segmentType.title}'; + } if (_blockListener == null && autoPlay && player != null) { final currPos = currPosInMilliseconds; - if (currPos >= segmentModel.segment.first && - currPos < segmentModel.segment.second) { + if (segmentModel.segment.contains(currPos)) { _lastBlockPos = currPos; switch (segmentModel.skipType) { @@ -182,7 +159,7 @@ mixin BlockMixin on GetxController { return true; } return false; - }); + }, orElse: () => false); } break; case SkipType.skipManually: @@ -202,12 +179,12 @@ mixin BlockMixin on GetxController { // _segmentProgressList segmentProgressList.addAll( _segmentList.map((e) { - double start = (e.segment.first / duration).clamp(0.0, 1.0); - double end = (e.segment.second / duration).clamp(0.0, 1.0); + double start = (e.segment.$1 / duration).clamp(0.0, 1.0); + double end = (e.segment.$2 / duration).clamp(0.0, 1.0); return Segment( start: start, 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; listData.insert(0, item); 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( 'onRemoveItem', const Duration(milliseconds: 500), @@ -252,7 +229,7 @@ mixin BlockMixin on GetxController { ); } - Widget buildItem(dynamic item, Animation animation) => + Widget buildItem(Object item, Animation animation) => throw UnimplementedError(); void _stopSkipTimer() { @@ -264,6 +241,15 @@ mixin BlockMixin on GetxController { Future? 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 onSkip( SegmentModel item, { bool isSkip = true, @@ -271,16 +257,11 @@ mixin BlockMixin on GetxController { }) async { try { await seekTo( - Duration(milliseconds: item.segment.second), + Duration(milliseconds: item.segment.$2), isSeek: isSeek, ); if (isSkip) { - if (autoPlay && Pref.blockToast) { - _showBlockToast('已跳过${item.segmentType.shortTitle}片段'); - } - if (isBlock && Pref.blockTrack) { - SponsorBlock.viewedVideoSponsorTime(item.UUID); - } + _skipToast(item); } else { _showBlockToast('已跳至${item.segmentType.shortTitle}'); } @@ -316,7 +297,7 @@ mixin BlockMixin on GetxController { title: const Text('赞成票', style: TextStyle(fontSize: 14)), onTap: () { Get.back(); - _doVote(segment.UUID, 1); + _doVote(segment.uuid, 1); }, ), ListTile( @@ -324,7 +305,7 @@ mixin BlockMixin on GetxController { title: const Text('反对票', style: TextStyle(fontSize: 14)), onTap: () { Get.back(); - _doVote(segment.UUID, 0); + _doVote(segment.uuid, 0); }, ), ListTile( @@ -363,7 +344,7 @@ mixin BlockMixin on GetxController { onTap: () { Get.back(); SponsorBlock.voteOnSponsorTime( - uuid: segment.UUID, + uuid: segment.uuid, category: item, ).then((i) { SmartDialog.showToast( @@ -381,7 +362,7 @@ mixin BlockMixin on GetxController { width: 10, decoration: BoxDecoration( shape: BoxShape.circle, - color: _getBlockColor(item), + color: blockConfig._getColor(item), ), ), style: const TextStyle(fontSize: 14, height: 1), @@ -431,7 +412,7 @@ mixin BlockMixin on GetxController { width: 10, decoration: BoxDecoration( shape: BoxShape.circle, - color: _getBlockColor(item.segmentType), + color: blockConfig._getColor(item.segmentType), ), ), style: const TextStyle(fontSize: 14, height: 1), @@ -445,7 +426,7 @@ mixin BlockMixin on GetxController { ), contentPadding: const EdgeInsets.only(left: 16, right: 8), 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), ), trailing: Row( @@ -455,7 +436,7 @@ mixin BlockMixin on GetxController { item.skipType.label, style: const TextStyle(fontSize: 13), ), - if (item.segment.second != 0) + if (item.segment.$2 != 0) SizedBox( width: 36, height: 36, @@ -514,6 +495,30 @@ mixin BlockMixin on GetxController { 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 void onClose() { _stopSkipTimer(); diff --git a/lib/pages/sponsor_block/view.dart b/lib/pages/sponsor_block/view.dart index d4ed379c9..a3fbf08dd 100644 --- a/lib/pages/sponsor_block/view.dart +++ b/lib/pages/sponsor_block/view.dart @@ -31,7 +31,7 @@ class _SponsorBlockPageState extends State { final _url = 'https://github.com/hanydd/BilibiliSponsorBlock'; final _textController = TextEditingController(); double _blockLimit = Pref.blockLimit; - final List> _blockSettings = Pref.blockSettings; + final _blockSettings = Pref.blockSettings; final List _blockColor = Pref.blockColor; String _userId = Pref.blockUserID; bool _blockToast = Pref.blockToast; diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index 7c220fec7..dafe039f6 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -508,7 +508,7 @@ class VideoDetailController extends GetxController @override - Widget buildItem(dynamic item, Animation animation) { + Widget buildItem(Object item, Animation animation) { final theme = Get.theme; return Align( alignment: Alignment.centerLeft, @@ -536,7 +536,7 @@ class VideoDetailController extends GetxController fontSize: 14, text: item is SegmentModel ? '跳过: ${item.segmentType.shortTitle}' - : '上次看到第${item + 1}P,点击跳转', + : '上次看到第${(item as int) + 1}P,点击跳转', onTap: (_) { if (item is int) { try { @@ -679,6 +679,10 @@ class VideoDetailController extends GetxController Volume? volume, }) async { final onlyPlayAudio = plPlayerController.onlyPlayAudio.value; + Duration? seek = seekToTime ?? defaultST ?? playedTime; + if (seek == null || seek == Duration.zero) { + seek = getFirstSegment(); + } await plPlayerController.setDataSource( DataSource( videoSource: isFileSource @@ -695,7 +699,7 @@ class VideoDetailController extends GetxController 'referer': HttpString.baseUrl, }, ), - seekTo: seekToTime ?? defaultST ?? playedTime, + seekTo: seek, duration: duration ?? (data.timeLength == null @@ -803,16 +807,11 @@ class VideoDetailController extends GetxController volume = data.volume; - final progress = args['progress']; + final progress = args.remove('progress'); if (progress != null) { this.defaultST = Duration(milliseconds: progress); - args['progress'] = null; - } else { - this.defaultST = - defaultST ?? - (data.lastPlayTime == null - ? Duration.zero - : Duration(milliseconds: data.lastPlayTime!)); + } else if (defaultST == null && data.lastPlayTime != null) { + this.defaultST = Duration(milliseconds: data.lastPlayTime!); } if (!isUgc && !fromReset && plPlayerController.enablePgcSkip) { diff --git a/lib/pages/video/post_panel/view.dart b/lib/pages/video/post_panel/view.dart index 9e0b6838b..25f6be9c2 100644 --- a/lib/pages/video/post_panel/view.dart +++ b/lib/pages/video/post_panel/view.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:math'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; @@ -473,14 +474,14 @@ class _PostPanelState extends State await videoCtr.play(); } final delay = start - seek; - if (delay > 0) { - await Future.delayed(Duration(milliseconds: delay)); - } - videoCtr.seek( - Duration( - milliseconds: (item.segment.second * 1000).round(), - ), + Future seekTo() => videoCtr.seek( + Duration(milliseconds: (item.segment.second * 1000).round()), ); + if (delay > 0) { + Timer(Duration(milliseconds: delay), seekTo); + } else { + seekTo(); + } } }, ), diff --git a/lib/utils/storage_pref.dart b/lib/utils/storage_pref.dart index 26abf286a..c5838d3f1 100644 --- a/lib/utils/storage_pref.dart +++ b/lib/utils/storage_pref.dart @@ -39,6 +39,7 @@ import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:crypto/crypto.dart'; import 'package:flex_seed_scheme/flex_seed_scheme.dart' show FlexSchemeVariant; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; @@ -324,7 +325,7 @@ abstract final class Pref { ); static bool get blockTrack => - _setting.get(SettingBoxKey.blockTrack, defaultValue: true); + _setting.get(SettingBoxKey.blockTrack, defaultValue: !kDebugMode); static bool get checkDynamic => _setting.get(SettingBoxKey.checkDynamic, defaultValue: true);