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

@@ -25,8 +25,6 @@ import 'package:PiliPalaX/utils/storage.dart';
import 'package:screen_brightness/screen_brightness.dart';
import 'package:universal_platform/universal_platform.dart';
import '../../models/video/play/subtitle.dart';
Box videoStorage = GStorage.video;
Box setting = GStorage.setting;
Box localCache = GStorage.localCache;
@@ -104,8 +102,9 @@ class PlPlayerController {
bool _enableHeart = true;
late DataSource dataSource;
final RxList<Map<String, String>> _vttSubtitles = <Map<String, String>>[].obs;
final RxInt _vttSubtitlesIndex = 0.obs;
// 视频字幕
final RxList<Map<String, String>> vttSubtitles = <Map<String, String>>[].obs;
final RxInt vttSubtitlesIndex = 0.obs;
Timer? _timer;
Timer? _timerForSeek;
@@ -115,6 +114,7 @@ class PlPlayerController {
Timer? timerForTrackingMouse;
final RxList<Segment> viewPointList = <Segment>[].obs;
final RxBool showVP = true.obs;
final RxList<Segment> segmentList = <Segment>[].obs;
// final Durations durations;
@@ -164,10 +164,6 @@ class PlPlayerController {
Rx<bool> get mute => _mute;
Stream<bool> get onMuteChanged => _mute.stream;
// 视频字幕
RxList<Map<String, String>> get vttSubtitles => _vttSubtitles;
RxInt get vttSubtitlesIndex => _vttSubtitlesIndex;
/// [videoPlayerController] instance of Player
Player? get videoPlayerController => _videoPlayerController;
@@ -408,6 +404,9 @@ class PlPlayerController {
Future<void> setDataSource(
DataSource dataSource, {
List<Segment>? segmentList,
List<Segment>? viewPointList,
List<Map<String, String>>? vttSubtitles,
int? vttSubtitlesIndex,
bool autoplay = true,
// 默认不循环
PlaylistMode looping = PlaylistMode.none,
@@ -431,8 +430,10 @@ class PlPlayerController {
}) async {
try {
this.dataSource = dataSource;
viewPointList.clear();
this.segmentList.value = segmentList ?? <Segment>[];
this.viewPointList.value = viewPointList ?? <Segment>[];
this.vttSubtitles.value = vttSubtitles ?? <Map<String, String>>[];
this.vttSubtitlesIndex.value = vttSubtitlesIndex ?? 0;
_autoPlay = autoplay;
_looping = looping;
// 初始化视频倍速
@@ -467,35 +468,7 @@ class PlPlayerController {
startListeners();
}
await _initializePlayer(seekTo: seekTo);
if (videoType.value != 'live' && _cid != 0) {
refreshSubtitles().then((value) {
if (_vttSubtitles.isNotEmpty) {
if (_vttSubtitlesIndex > 0 &&
_vttSubtitlesIndex < _vttSubtitles.length) {
setSubtitle(_vttSubtitlesIndex.value);
} else {
String preference = setting.get(SettingBoxKey.subtitlePreference,
defaultValue: SubtitlePreference.values.first.code);
if (preference == 'on') {
setSubtitle(1);
} else if (preference == 'withoutAi') {
bool found = false;
for (int i = 1; i < _vttSubtitles.length; i++) {
if (_vttSubtitles[i]['language']!.startsWith('ai')) {
continue;
}
found = true;
setSubtitle(i);
break;
}
if (!found) _vttSubtitlesIndex.value = 0;
} else {
_vttSubtitlesIndex.value = 0;
}
}
}
});
}
setSubtitle(this.vttSubtitlesIndex.value);
} catch (err, stackTrace) {
dataStatus.status.value = DataStatus.error;
debugPrint(stackTrace.toString());
@@ -1353,49 +1326,20 @@ class PlPlayerController {
}
}
Future refreshSubtitles() async {
_vttSubtitles.clear();
Map res = await VideoHttp.subtitlesJson(bvid: _bvid, cid: _cid);
// 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.value = result;
}
// if (_vttSubtitles.isEmpty) {
// SmartDialog.showToast('字幕均加载失败');
// }
}
if (res["view_points"] is List && res["view_points"].isNotEmpty) {
viewPointList.value = (res["view_points"] as List).map((item) {
double start = (item['to'] / durationSeconds.value).clamp(0.0, 1.0);
return Segment(
start,
start,
Colors.black,
item['content'],
);
}).toList();
}
}
// 设定字幕轨道
setSubtitle(int index) {
if (index == 0) {
_videoPlayerController?.setSubtitleTrack(SubtitleTrack.no());
_vttSubtitlesIndex.value = 0;
vttSubtitlesIndex.value = 0;
return;
}
Map<String, String> s = _vttSubtitles[index];
Map<String, String> s = vttSubtitles[index];
_videoPlayerController?.setSubtitleTrack(SubtitleTrack.data(
s['text']!,
title: s['title']!,
language: s['language']!,
));
_vttSubtitlesIndex.value = index;
vttSubtitlesIndex.value = index;
}
static void updatePlayCount() {

View File

@@ -10,4 +10,5 @@ enum BottomControlType {
speed,
fullscreen,
custom,
viewPoints,
}

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:math';
import 'package:PiliPalaX/common/widgets/segment_progress_bar.dart';
import 'package:PiliPalaX/http/loading_state.dart';
@@ -47,6 +48,7 @@ class PLVideoPlayer extends StatefulWidget {
this.customWidget,
this.customWidgets,
this.showEpisodes,
this.showViewPoints,
super.key,
});
@@ -62,6 +64,7 @@ class PLVideoPlayer extends StatefulWidget {
final Widget? customWidget;
final List<Widget>? customWidgets;
final Function? showEpisodes;
final VoidCallback? showViewPoints;
@override
State<PLVideoPlayer> createState() => _PLVideoPlayerState();
@@ -236,7 +239,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
Map<BottomControlType, Widget> videoProgressWidgets = {
/// 上一集
BottomControlType.pre: Container(
width: 42,
width: 35,
height: 30,
alignment: Alignment.center,
child: ComBtn(
@@ -268,7 +271,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
/// 下一集
BottomControlType.next: Container(
width: 42,
width: 35,
height: 30,
alignment: Alignment.center,
child: ComBtn(
@@ -330,9 +333,32 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
/// 空白占位
BottomControlType.space: const Spacer(),
/// 分段信息
BottomControlType.viewPoints: Obx(
() => plPlayerController.viewPointList.isEmpty
? const SizedBox.shrink()
: Container(
width: 35,
height: 30,
alignment: Alignment.center,
child: ComBtn(
icon: Transform.rotate(
angle: pi / 2,
child: const Icon(
Icons.reorder,
semanticLabel: '分段信息',
size: 22,
color: Colors.white,
),
),
fuc: widget.showViewPoints,
),
),
),
/// 选集
BottomControlType.episode: Container(
width: 42,
width: 35,
height: 30,
alignment: Alignment.center,
child: ComBtn(
@@ -387,7 +413,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
/// 画面比例
BottomControlType.fit: SizedBox(
width: 42,
width: 35,
height: 30,
child: TextButton(
onPressed: () => plPlayerController.toggleVideoFit(),
@@ -408,7 +434,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
() => plPlayerController.vttSubtitles.isEmpty
? const SizedBox.shrink()
: SizedBox(
width: 42,
width: 35,
height: 30,
child: PopupMenuButton<int>(
onSelected: (int value) {
@@ -434,7 +460,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
}).toList();
},
child: Container(
width: 42,
width: 35,
height: 30,
alignment: Alignment.center,
child: const Icon(
@@ -450,7 +476,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
/// 播放速度
BottomControlType.speed: SizedBox(
width: 42,
width: 35,
height: 30,
child: PopupMenuButton<double>(
onSelected: (double value) {
@@ -473,7 +499,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
}).toList();
},
child: Container(
width: 42,
width: 35,
height: 30,
alignment: Alignment.center,
child: Obx(() => Text("${plPlayerController.playbackSpeed}X",
@@ -485,7 +511,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
/// 全屏
BottomControlType.fullscreen: SizedBox(
width: 42,
width: 35,
height: 30,
child: Obx(() => ComBtn(
icon: Icon(
@@ -510,6 +536,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
if (anySeason) BottomControlType.pre,
if (anySeason) BottomControlType.next,
BottomControlType.space,
BottomControlType.viewPoints,
if (anySeason) BottomControlType.episode,
if (plPlayerController.isFullScreen.value) BottomControlType.fit,
BottomControlType.subtitle,
@@ -1114,7 +1141,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
segmentColors: plPlayerController.segmentList,
),
),
if (plPlayerController.viewPointList.isNotEmpty)
if (plPlayerController.viewPointList.isNotEmpty &&
plPlayerController.showVP.value)
CustomPaint(
size: Size(double.infinity, 3.5),
painter: SegmentProgressBar(

View File

@@ -104,7 +104,8 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
segmentColors: controller!.segmentList,
),
),
if (controller?.viewPointList.isNotEmpty == true)
if (controller?.viewPointList.isNotEmpty == true &&
controller?.showVP.value == true)
CustomPaint(
size: Size(double.infinity, 3.5),
painter: SegmentProgressBar(