opt: video subtitle

avoid refetching subtitle
fix stuck when parsing large subtitle body

opt: viewpoints

Update README.md

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2024-12-02 12:54:23 +08:00
parent a0b1e23727
commit cbdd8e77db
15 changed files with 348 additions and 121 deletions

View File

@@ -8,6 +8,7 @@ import 'package:PiliPalaX/common/widgets/pair.dart';
import 'package:PiliPalaX/common/widgets/segment_progress_bar.dart';
import 'package:PiliPalaX/http/danmaku.dart';
import 'package:PiliPalaX/http/init.dart';
import 'package:PiliPalaX/models/video/play/subtitle.dart';
import 'package:PiliPalaX/utils/extension.dart';
import 'package:dio/dio.dart';
import 'package:floating/floating.dart';
@@ -284,6 +285,7 @@ class VideoDetailController extends GetxController
List<Pair<SegmentType, SkipType>>? _blockSettings;
List<Color>? _blockColor;
RxList<SegmentModel> segmentList = <SegmentModel>[].obs;
List<Segment> viewPointList = <Segment>[];
List<Segment>? _segmentProgressList;
Color _getColor(SegmentType segment) =>
_blockColor?[segment.index] ?? segment.color;
@@ -844,6 +846,9 @@ class VideoDetailController extends GetxController
},
),
segmentList: _segmentProgressList,
viewPointList: viewPointList,
vttSubtitles: _vttSubtitles,
vttSubtitlesIndex: vttSubtitlesIndex,
// 硬解
enableHA: enableHA.value,
hwdec: hwdec.value,
@@ -877,6 +882,7 @@ class VideoDetailController extends GetxController
if (enableSponsorBlock) {
await _querySponsorBlock();
}
_getSubtitle();
if (data.acceptDesc!.isNotEmpty && data.acceptDesc!.contains('试看')) {
SmartDialog.showToast(
'该视频为专属视频,仅提供试看',
@@ -1020,7 +1026,6 @@ class VideoDetailController extends GetxController
List<PostSegmentModel>? list;
void onBlock(BuildContext context) {
PersistentBottomSheetController? ctr;
list ??= <PostSegmentModel>[];
if (list!.isEmpty) {
list!.add(
@@ -1034,18 +1039,18 @@ class VideoDetailController extends GetxController
),
);
}
ctr = plPlayerController.isFullScreen.value
plPlayerController.isFullScreen.value
? scaffoldKey.currentState?.showBottomSheet(
enableDrag: false,
(context) => _postPanel(ctr?.close, false),
(context) => _postPanel(false),
)
: childKey.currentState?.showBottomSheet(
enableDrag: false,
(context) => _postPanel(ctr?.close),
(context) => _postPanel(),
);
}
Widget _postPanel(onClose, [bool isChild = true]) => StatefulBuilder(
Widget _postPanel([bool isChild = true]) => StatefulBuilder(
builder: (context, setState) {
void updateSegment({
required bool isFirst,
@@ -1197,7 +1202,7 @@ class VideoDetailController extends GetxController
iconButton(
context: context,
tooltip: '关闭',
onPressed: onClose,
onPressed: Get.back,
icon: Icons.close,
),
const SizedBox(width: 16),
@@ -1572,4 +1577,73 @@ class VideoDetailController extends GetxController
SegmentType.exclusive_access => [ActionType.full],
};
}
List<Map<String, String>> _vttSubtitles = <Map<String, String>>[];
int vttSubtitlesIndex = 0;
void _getSubtitle() {
_querySubtitles().then((value) {
if (_vttSubtitles.isNotEmpty) {
String preference = setting.get(
SettingBoxKey.subtitlePreference,
defaultValue: SubtitlePreference.values.first.code,
);
if (preference == 'on') {
vttSubtitlesIndex = 1;
} else if (preference == 'withoutAi') {
for (int i = 1; i < _vttSubtitles.length; i++) {
if (_vttSubtitles[i]['language']!.startsWith('ai')) {
continue;
}
vttSubtitlesIndex = i;
break;
}
}
if (plPlayerController.vttSubtitles.isEmpty) {
plPlayerController.vttSubtitles.value = _vttSubtitles;
plPlayerController.vttSubtitlesIndex.value = vttSubtitlesIndex;
if (vttSubtitlesIndex != 0) {
plPlayerController.setSubtitle(vttSubtitlesIndex);
}
}
}
});
}
Future _querySubtitles() async {
Map res = await VideoHttp.subtitlesJson(bvid: bvid, cid: cid.value);
// if (!res["status"]) {
// SmartDialog.showToast('查询字幕错误,${res["msg"]}');
// }
if (res["data"] is List && res["data"].isNotEmpty) {
var result = await VideoHttp.vttSubtitles(res["data"]);
if (result != null) {
_vttSubtitles = result;
}
// if (_vttSubtitles.isEmpty) {
// SmartDialog.showToast('字幕均加载失败');
// }
}
if (GStorage.showViewPoints &&
res["view_points"] is List &&
res["view_points"].isNotEmpty) {
viewPointList = (res["view_points"] as List).map((item) {
double start =
(item['to'] / ((data.timeLength ?? 0) / 1000)).clamp(0.0, 1.0);
return Segment(
start,
start,
Colors.black87,
item?['content'],
item?['imgUrl'],
item?['from'],
item?['to'],
);
}).toList();
if (plPlayerController.viewPointList.isEmpty) {
plPlayerController.viewPointList.value = viewPointList;
}
}
}
}

View File

@@ -3,7 +3,9 @@ import 'dart:io';
import 'dart:math';
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/icon_button.dart';
import 'package:PiliPalaX/common/widgets/list_sheet.dart';
import 'package:PiliPalaX/common/widgets/segment_progress_bar.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/models/bangumi/info.dart';
import 'package:PiliPalaX/models/common/reply_type.dart';
@@ -16,6 +18,7 @@ import 'package:PiliPalaX/pages/video/detail/widgets/ai_detail.dart';
import 'package:PiliPalaX/utils/extension.dart';
import 'package:PiliPalaX/utils/global_data.dart';
import 'package:PiliPalaX/utils/id_utils.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:auto_orientation/auto_orientation.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:floating/floating.dart';
@@ -350,6 +353,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
}
if (plPlayerController != null) {
_makeHeartBeat();
videoDetailController.vttSubtitlesIndex =
plPlayerController!.vttSubtitlesIndex.value;
videoDetailController.defaultST = plPlayerController!.position.value;
plPlayerController!.removeStatusLister(playerListener);
plPlayerController!.pause();
@@ -960,6 +965,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
),
),
showEpisodes: showEpisodes,
showViewPoints: showViewPoints,
),
);
@@ -1058,6 +1064,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
),
),
showEpisodes: showEpisodes,
showViewPoints: showViewPoints,
),
);
} else {
@@ -1379,8 +1386,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
}
showEpisodes(index, season, episodes, bvid, aid, cid) {
PersistentBottomSheetController? bottomSheetController;
Widget listSheetContent() => ListSheetContent(
index: index,
season: season,
@@ -1392,15 +1397,162 @@ class _VideoDetailPageState extends State<VideoDetailPage>
videoDetailController.videoType == SearchType.media_bangumi
? bangumiIntroController.changeSeasonOrbangu
: videoIntroController.changeSeasonOrbangu,
onClose: bottomSheetController?.close,
);
if (isFullScreen) {
videoDetailController.scaffoldKey.currentState?.showBottomSheet(
(context) => listSheetContent(),
);
} else {
videoDetailController.childKey.currentState?.showBottomSheet(
(context) => listSheetContent(),
);
}
}
bottomSheetController = isFullScreen
? videoDetailController.scaffoldKey.currentState?.showBottomSheet(
(context) => listSheetContent(),
)
: videoDetailController.scaffoldKey.currentState?.showBottomSheet(
(context) => listSheetContent(),
);
void showViewPoints() {
Widget listSheetContent(context, [bool isFS = false]) {
int currentIndex = -1;
return StatefulBuilder(
builder: (context, setState) => SizedBox(
height: isFS ? Utils.getSheetHeight(context) : null,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
titleSpacing: 16,
title: Text('分段信息'),
actions: [
Text(
'分段进度条',
style: TextStyle(fontSize: 16),
),
Obx(
() => Transform.scale(
alignment: Alignment.centerLeft,
scale: 0.8,
child: Switch(
thumbIcon:
WidgetStateProperty.resolveWith<Icon?>((states) {
if (states.isNotEmpty &&
states.first == WidgetState.selected) {
return const Icon(Icons.done);
}
return null;
}),
value:
videoDetailController.plPlayerController.showVP.value,
onChanged: (value) {
videoDetailController.plPlayerController.showVP.value =
value;
},
),
),
),
iconButton(
context: context,
size: 30,
icon: Icons.clear,
tooltip: '关闭',
onPressed: Get.back,
),
const SizedBox(width: 16),
],
),
body: SingleChildScrollView(
child: Column(
children: [
...List.generate(videoDetailController.viewPointList.length,
(index) {
Segment segment =
videoDetailController.viewPointList[index];
if (currentIndex == -1 &&
segment.from != null &&
segment.to != null) {
if (videoDetailController
.plPlayerController.positionSeconds.value >=
segment.from! &&
videoDetailController
.plPlayerController.positionSeconds.value <
segment.to!) {
currentIndex = index;
}
}
return ListTile(
dense: true,
onTap: segment.from != null
? () {
currentIndex = index;
plPlayerController?.danmakuController?.clear();
plPlayerController?.videoPlayerController
?.seek(Duration(seconds: segment.from!));
setState(() {});
}
: null,
leading: segment.url?.isNotEmpty == true
? Container(
margin: const EdgeInsets.symmetric(vertical: 6),
decoration: currentIndex == index
? BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(
width: 1.8,
strokeAlign:
BorderSide.strokeAlignOutside,
color: Theme.of(context)
.colorScheme
.primary,
),
)
: null,
child: LayoutBuilder(
builder: (_, constraints) => NetworkImgLayer(
radius: 6,
src: segment.url,
width: constraints.maxHeight *
StyleString.aspectRatio,
height: constraints.maxHeight,
),
),
)
: null,
title: Text(
segment.title ?? '',
style: TextStyle(
fontSize: 14,
fontWeight:
currentIndex == index ? FontWeight.bold : null,
color: currentIndex == index
? Theme.of(context).colorScheme.primary
: null,
),
),
subtitle: Text(
'${segment.from != null ? Utils.timeFormat(segment.from) : ''} - ${segment.to != null ? Utils.timeFormat(segment.to) : ''}',
style: TextStyle(
fontSize: 13,
color: currentIndex == index
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline,
),
),
);
}),
SizedBox(height: 25 + MediaQuery.paddingOf(context).bottom),
],
),
),
),
),
);
}
if (isFullScreen) {
videoDetailController.scaffoldKey.currentState?.showBottomSheet(
(context) => listSheetContent(context, true),
);
} else {
videoDetailController.childKey.currentState?.showBottomSheet(
(context) => listSheetContent(context),
);
}
}
}