Merge branch 'feature-sponsorBlock'
This commit is contained in:
@ -615,4 +615,8 @@ class Api {
|
||||
|
||||
/// 订阅状态
|
||||
static const String videoRelation = '/x/web-interface/archive/relation';
|
||||
|
||||
/// 获取空降区间
|
||||
static const String getSkipSegments =
|
||||
'${HttpString.sponsorBlockBaseUrl}/api/skipSegments';
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'package:pilipala/models/sponsor_block/segment.dart';
|
||||
|
||||
import 'index.dart';
|
||||
|
||||
class CommonHttp {
|
||||
@ -14,4 +16,32 @@ class CommonHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future querySkipSegments({required String bvid}) async {
|
||||
var res = await Request().get(Api.getSkipSegments, data: {
|
||||
'videoID': bvid,
|
||||
});
|
||||
print(res.data);
|
||||
if (res.data is List && res.data.isNotEmpty) {
|
||||
try {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data
|
||||
.map<SegmentDataModel>((e) => SegmentDataModel.fromJson(e))
|
||||
.toList(),
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': 'sponsorBlock数据解析失败: $err',
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': true,
|
||||
'data': [],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ class HttpString {
|
||||
static const String passBaseUrl = 'https://passport.bilibili.com';
|
||||
static const String messageBaseUrl = 'https://message.bilibili.com';
|
||||
static const String bangumiBaseUrl = 'https://bili.meark.me';
|
||||
static const String sponsorBlockBaseUrl = 'https://www.bsbsb.top';
|
||||
static const List<int> validateStatusCodes = [
|
||||
302,
|
||||
304,
|
||||
|
26
lib/models/sponsor_block/action_type.dart
Normal file
26
lib/models/sponsor_block/action_type.dart
Normal file
@ -0,0 +1,26 @@
|
||||
// 片段类型枚举
|
||||
enum ActionType {
|
||||
skip,
|
||||
mute,
|
||||
full,
|
||||
poi,
|
||||
chapter,
|
||||
}
|
||||
|
||||
extension ActionTypeExtension on ActionType {
|
||||
String get value => [
|
||||
'skip',
|
||||
'mute',
|
||||
'full',
|
||||
'poi',
|
||||
'chapter',
|
||||
][index];
|
||||
|
||||
String get label => [
|
||||
'跳过',
|
||||
'静音',
|
||||
'完整观看',
|
||||
'亮点',
|
||||
'章节切换',
|
||||
][index];
|
||||
}
|
43
lib/models/sponsor_block/segment.dart
Normal file
43
lib/models/sponsor_block/segment.dart
Normal file
@ -0,0 +1,43 @@
|
||||
import 'action_type.dart';
|
||||
import 'segment_type.dart';
|
||||
|
||||
class SegmentDataModel {
|
||||
final SegmentType? category;
|
||||
final ActionType? actionType;
|
||||
final List? segment;
|
||||
final String? uuid;
|
||||
final num? videoDuration;
|
||||
final int? locked;
|
||||
final int? votes;
|
||||
final String? description;
|
||||
// 是否已经跳过
|
||||
bool isSkip = false;
|
||||
|
||||
SegmentDataModel({
|
||||
this.category,
|
||||
this.actionType,
|
||||
this.segment,
|
||||
this.uuid,
|
||||
this.videoDuration,
|
||||
this.locked,
|
||||
this.votes,
|
||||
this.description,
|
||||
});
|
||||
|
||||
factory SegmentDataModel.fromJson(Map<String, dynamic> json) {
|
||||
return SegmentDataModel(
|
||||
category: SegmentType.values.firstWhere(
|
||||
(e) => e.value == json['category'],
|
||||
orElse: () => SegmentType.sponsor),
|
||||
actionType: ActionType.values.firstWhere(
|
||||
(e) => e.value == json['actionType'],
|
||||
orElse: () => ActionType.skip),
|
||||
segment: json['segment'],
|
||||
uuid: json['UUID'],
|
||||
videoDuration: json['videoDuration'],
|
||||
locked: json['locked'],
|
||||
votes: json['votes'],
|
||||
description: json['description'],
|
||||
);
|
||||
}
|
||||
}
|
46
lib/models/sponsor_block/segment_type.dart
Normal file
46
lib/models/sponsor_block/segment_type.dart
Normal file
@ -0,0 +1,46 @@
|
||||
// 片段类型枚举
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
enum SegmentType {
|
||||
sponsor,
|
||||
intro,
|
||||
outro,
|
||||
interaction,
|
||||
selfpromo,
|
||||
music_offtopic,
|
||||
preview,
|
||||
poi_highlight,
|
||||
filler,
|
||||
exclusive_access,
|
||||
chapter,
|
||||
}
|
||||
|
||||
extension SegmentTypeExtension on SegmentType {
|
||||
String get value => [
|
||||
'sponsor',
|
||||
'intro',
|
||||
'outro',
|
||||
'interaction',
|
||||
'selfpromo',
|
||||
'music_offtopic',
|
||||
'preview',
|
||||
'poi_highlight',
|
||||
'filler',
|
||||
'exclusive_access',
|
||||
'chapter',
|
||||
][index];
|
||||
|
||||
String get label => [
|
||||
'赞助',
|
||||
'开场介绍',
|
||||
'片尾致谢',
|
||||
'互动',
|
||||
'自我推广',
|
||||
'音乐',
|
||||
'预览',
|
||||
'亮点',
|
||||
'无效填充',
|
||||
'独家访问',
|
||||
'章节',
|
||||
][index];
|
||||
}
|
@ -6,11 +6,14 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ns_danmaku/ns_danmaku.dart';
|
||||
import 'package:pilipala/http/common.dart';
|
||||
import 'package:pilipala/http/constants.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/common/reply_type.dart';
|
||||
import 'package:pilipala/models/common/search_type.dart';
|
||||
import 'package:pilipala/models/sponsor_block/segment.dart';
|
||||
import 'package:pilipala/models/sponsor_block/segment_type.dart';
|
||||
import 'package:pilipala/models/video/later.dart';
|
||||
import 'package:pilipala/models/video/play/quality.dart';
|
||||
import 'package:pilipala/models/video/play/url.dart';
|
||||
@ -120,6 +123,8 @@ class VideoDetailController extends GetxController
|
||||
RxBool isWatchLaterVisible = false.obs;
|
||||
RxString watchLaterTitle = ''.obs;
|
||||
RxInt watchLaterCount = 0.obs;
|
||||
List<SegmentDataModel> skipSegments = <SegmentDataModel>[];
|
||||
int? lastPosition;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -188,6 +193,11 @@ class VideoDetailController extends GetxController
|
||||
tabCtr.addListener(() {
|
||||
onTabChanged();
|
||||
});
|
||||
|
||||
/// 仅投稿视频skip
|
||||
if (videoType == SearchType.video) {
|
||||
querySkipSegments();
|
||||
}
|
||||
}
|
||||
|
||||
showReplyReplyPanel(oid, fRpid, firstFloor, currentReply, loadMore) {
|
||||
@ -305,6 +315,7 @@ class VideoDetailController extends GetxController
|
||||
plPlayerController.headerControl = headerControl;
|
||||
|
||||
plPlayerController.subtitles.value = subtitles;
|
||||
onPositionChanged();
|
||||
}
|
||||
|
||||
// 视频链接
|
||||
@ -706,6 +717,53 @@ class VideoDetailController extends GetxController
|
||||
isWatchLaterVisible.value = tabCtr.index == 0;
|
||||
}
|
||||
|
||||
// 获取sponsorBlock数据
|
||||
Future querySkipSegments() async {
|
||||
var res = await CommonHttp.querySkipSegments(bvid: bvid);
|
||||
if (res['status']) {
|
||||
/// TODO 根据segmentType过滤数据
|
||||
skipSegments = res['data'];
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
// 监听视频进度
|
||||
void onPositionChanged() async {
|
||||
if (skipSegments.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
plPlayerController.videoPlayerController?.stream.position
|
||||
.listen((Duration position) async {
|
||||
final int positionMs = position.inSeconds;
|
||||
|
||||
// 如果当前秒与上次处理的秒相同,则直接返回
|
||||
if (lastPosition != null && lastPosition! == positionMs) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastPosition = positionMs;
|
||||
|
||||
for (SegmentDataModel segment in skipSegments) {
|
||||
try {
|
||||
final segmentStart = segment.segment!.first.toInt();
|
||||
final segmentEnd = segment.segment!.last.toInt();
|
||||
|
||||
/// 只有顺序播放时才skip,跳转时间点不会skip
|
||||
if (positionMs == segmentStart && !segment.isSkip) {
|
||||
await plPlayerController.videoPlayerController
|
||||
?.seek(Duration(seconds: segmentEnd));
|
||||
segment.isSkip = true;
|
||||
SmartDialog.showToast('已跳过${segment.category!.label}片段');
|
||||
}
|
||||
} catch (err) {
|
||||
SmartDialog.showToast('skipSegments error: $err');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
super.onClose();
|
||||
|
20
pubspec.lock
20
pubspec.lock
@ -533,18 +533,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492"
|
||||
sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.9.2+1"
|
||||
version: "0.9.3"
|
||||
file_selector_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: cb284e267f8e2a45a904b5c094d2ba51d0aabfc20b1538ab786d9ef7dc2bf75c
|
||||
sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.9.4+1"
|
||||
version: "0.9.4+2"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -557,10 +557,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_windows
|
||||
sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69"
|
||||
sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.9.3+2"
|
||||
version: "0.9.3+3"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -842,10 +842,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447"
|
||||
sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.8.12"
|
||||
version: "0.8.12+1"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1438,10 +1438,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: scrollview_observer
|
||||
sha256: fa408bcfd41e19da841eb53fc471f8f952d5ef818b854d2505c4bb3f0c876381
|
||||
sha256: "8537ba32e5a15ade301e5c77ae858fd8591695defaad1821eca9eeb4ac28a157"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.22.0"
|
||||
version: "1.23.0"
|
||||
sentry:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
Reference in New Issue
Block a user