mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-07-04 16:20:14 +08:00
feat: progressbar: show viewpoints #28
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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']};
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设定字幕轨道
|
// 设定字幕轨道
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user