feat: progressbar: show viewpoints #28

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2024-12-01 16:01:57 +08:00
parent 43977c737b
commit f9ed31c65a
6 changed files with 81 additions and 31 deletions

View File

@@ -4,8 +4,9 @@ class Segment {
final double start; final double start;
final double end; final double end;
final Color color; final Color color;
final String? title;
Segment(this.start, this.end, this.color); Segment(this.start, this.end, this.color, [this.title]);
} }
class SegmentProgressBar extends CustomPainter { class SegmentProgressBar extends CustomPainter {
@@ -21,10 +22,10 @@ class SegmentProgressBar extends CustomPainter {
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final paint = Paint()..style = PaintingStyle.fill; final paint = Paint()..style = PaintingStyle.fill;
for (var segment in segmentColors) { for (int i = 0; i < segmentColors.length; i++) {
paint.color = segment.color; paint.color = segmentColors[i].color;
final segmentStart = segment.start * size.width; final segmentStart = segmentColors[i].start * size.width;
final segmentEnd = segment.end * size.width; final segmentEnd = segmentColors[i].end * size.width;
final progressEnd = progress * size.width; final progressEnd = progress * size.width;
if (progressEnd < segmentStart) { if (progressEnd < segmentStart) {
@@ -33,11 +34,35 @@ class SegmentProgressBar extends CustomPainter {
final segmentWidth = final segmentWidth =
(progressEnd < segmentEnd ? progressEnd : segmentEnd) - segmentStart; (progressEnd < segmentEnd ? progressEnd : segmentEnd) - segmentStart;
if (segmentWidth > 0) { if (segmentWidth >= 0) {
canvas.drawRect( canvas.drawRect(
Rect.fromLTWH(segmentStart, 0, segmentWidth, size.height), Rect.fromLTWH(
segmentStart,
0,
segmentWidth == 0 ? 2 : segmentWidth,
size.height,
),
paint, paint,
); );
if (segmentColors[i].title != null) {
TextPainter textPainter = TextPainter(
text: TextSpan(
text: segmentColors[i].title,
style: TextStyle(color: Colors.white, fontSize: 8),
),
textDirection: TextDirection.ltr,
)..layout();
double? prevStart;
if (i != 0) {
prevStart = segmentColors[i - 1].start * size.width;
}
double textX = i == 0
? (segmentStart - textPainter.width) / 2
: (segmentStart - prevStart! - textPainter.width) / 2 + prevStart;
double textY = size.height - textPainter.height - 1;
textPainter.paint(canvas, Offset(textX, textY));
}
} }
} }
} }

View File

@@ -861,11 +861,14 @@ class VideoHttp {
static Future subtitlesJson( static Future subtitlesJson(
{String? aid, String? bvid, required int cid}) async { {String? aid, String? bvid, required int cid}) async {
assert(aid != null || bvid != null); assert(aid != null || bvid != null);
var res = await Request().get(Api.subtitleUrl, data: { var res = await Request().get(
if (aid != null) 'aid': aid, Api.subtitleUrl,
if (bvid != null) 'bvid': bvid, data: {
'cid': cid, if (aid != null) 'aid': aid,
}); if (bvid != null) 'bvid': bvid,
'cid': cid,
},
);
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
dynamic data = res.data['data']; dynamic data = res.data['data'];
List subtitlesJson = data['subtitle']['subtitles']; List subtitlesJson = data['subtitle']['subtitles'];
@@ -887,6 +890,7 @@ class VideoHttp {
return { return {
'status': true, 'status': true,
'data': subtitlesJson, 'data': subtitlesJson,
'view_points': data['view_points'],
}; };
} else { } else {
return {'status': false, 'data': [], 'msg': res.data['message']}; return {'status': false, 'data': [], 'msg': res.data['message']};

View File

@@ -607,11 +607,7 @@ class VideoDetailController extends GetxController
.clamp(0.0, 1.0); .clamp(0.0, 1.0);
double end = (item.segment.second / ((data.timeLength ?? 0) / 1000)) double end = (item.segment.second / ((data.timeLength ?? 0) / 1000))
.clamp(0.0, 1.0); .clamp(0.0, 1.0);
return Segment( return Segment(start, end, _getColor(item.segmentType));
start,
(start == end && end != 0) ? (end + 0.01).clamp(0.0, 1.0) : end,
_getColor(item.segmentType),
);
}).toList()); }).toList());
} catch (e) { } catch (e) {
debugPrint('failed to parse sponsorblock: $e'); debugPrint('failed to parse sponsorblock: $e');

View File

@@ -114,6 +114,7 @@ class PlPlayerController {
Timer? _timerForGettingVolume; Timer? _timerForGettingVolume;
Timer? timerForTrackingMouse; Timer? timerForTrackingMouse;
final RxList<Segment> viewPointList = <Segment>[].obs;
final RxList<Segment> segmentList = <Segment>[].obs; final RxList<Segment> segmentList = <Segment>[].obs;
// final Durations durations; // final Durations durations;
@@ -430,6 +431,7 @@ class PlPlayerController {
}) async { }) async {
try { try {
this.dataSource = dataSource; this.dataSource = dataSource;
viewPointList.clear();
this.segmentList.value = segmentList ?? <Segment>[]; this.segmentList.value = segmentList ?? <Segment>[];
_autoPlay = autoplay; _autoPlay = autoplay;
_looping = looping; _looping = looping;
@@ -1357,17 +1359,27 @@ class PlPlayerController {
// if (!res["status"]) { // if (!res["status"]) {
// SmartDialog.showToast('查询字幕错误,${res["msg"]}'); // SmartDialog.showToast('查询字幕错误,${res["msg"]}');
// } // }
if (res["data"].length == 0) {
return; 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('字幕均加载失败');
// }
} }
var result = await VideoHttp.vttSubtitles(res["data"]); if (res["view_points"] is List && res["view_points"].isNotEmpty) {
if (result != null) { viewPointList.value = (res["view_points"] as List).map((item) {
_vttSubtitles.value = result; double start = (item['to'] / durationSeconds.value).clamp(0.0, 1.0);
return Segment(
start,
start,
Colors.black,
item['content'],
);
}).toList();
} }
// if (_vttSubtitles.isEmpty) {
// SmartDialog.showToast('字幕均加载失败');
// }
return;
} }
// 设定字幕轨道 // 设定字幕轨道

View File

@@ -1014,7 +1014,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
BottomControl( BottomControl(
controller: widget.controller, controller: widget.controller,
buildBottomControl: buildBottomControl(), buildBottomControl: buildBottomControl(),
segmentList: plPlayerController.segmentList,
), ),
), ),
), ),
@@ -1115,6 +1114,14 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
segmentColors: plPlayerController.segmentList, segmentColors: plPlayerController.segmentList,
), ),
), ),
if (plPlayerController.viewPointList.isNotEmpty)
CustomPaint(
size: Size(double.infinity, 3.5),
painter: SegmentProgressBar(
progress: 1,
segmentColors: plPlayerController.viewPointList,
),
),
], ],
), ),
// SlideTransition( // SlideTransition(

View File

@@ -13,11 +13,9 @@ import '../../../common/widgets/audio_video_progress_bar.dart';
class BottomControl extends StatelessWidget implements PreferredSizeWidget { class BottomControl extends StatelessWidget implements PreferredSizeWidget {
final PlPlayerController? controller; final PlPlayerController? controller;
final List<Widget>? buildBottomControl; final List<Widget>? buildBottomControl;
final List<Segment>? segmentList;
const BottomControl({ const BottomControl({
this.controller, this.controller,
this.buildBottomControl, this.buildBottomControl,
this.segmentList,
super.key, super.key,
}); });
@@ -98,12 +96,20 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
TextDirection.ltr); TextDirection.ltr);
}, },
), ),
if (segmentList?.isNotEmpty == true) if (controller?.segmentList.isNotEmpty == true)
CustomPaint( CustomPaint(
size: Size(double.infinity, 3.5), size: Size(double.infinity, 3.5),
painter: SegmentProgressBar( painter: SegmentProgressBar(
progress: 1, progress: 1,
segmentColors: segmentList!, segmentColors: controller!.segmentList,
),
),
if (controller?.viewPointList.isNotEmpty == true)
CustomPaint(
size: Size(double.infinity, 3.5),
painter: SegmentProgressBar(
progress: 1,
segmentColors: controller!.viewPointList,
), ),
), ),
], ],