mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-08-05 02:00:13 +08:00
refa: segment progressbar
Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
@@ -365,12 +365,15 @@ class RenderProgressBar extends RenderBox {
|
||||
_thumbGlowColor = thumbGlowColor,
|
||||
_thumbGlowRadius = thumbGlowRadius,
|
||||
_paintThumbGlow = thumbGlowRadius > thumbRadius,
|
||||
_thumbCanPaintOutsideBar = thumbCanPaintOutsideBar {
|
||||
_drag = _EagerHorizontalDragGestureRecognizer()
|
||||
..onStart = _onDragStart
|
||||
..onUpdate = _onDragUpdate
|
||||
..onEnd = _onDragEnd
|
||||
..onCancel = _finishDrag;
|
||||
_thumbCanPaintOutsideBar = thumbCanPaintOutsideBar,
|
||||
_hitTestSelf = onDragStart != null {
|
||||
if (onDragStart != null) {
|
||||
_drag = _EagerHorizontalDragGestureRecognizer()
|
||||
..onStart = _onDragStart
|
||||
..onUpdate = _onDragUpdate
|
||||
..onEnd = _onDragEnd
|
||||
..onCancel = _finishDrag;
|
||||
}
|
||||
if (!_userIsDraggingThumb) {
|
||||
_progress = progress;
|
||||
_thumbValue = _proportionOfTotal(_progress);
|
||||
@@ -380,6 +383,7 @@ class RenderProgressBar extends RenderBox {
|
||||
@override
|
||||
void dispose() {
|
||||
_drag?.dispose();
|
||||
_drag = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -659,8 +663,9 @@ class RenderProgressBar extends RenderBox {
|
||||
@override
|
||||
double computeMaxIntrinsicHeight(double width) => _heightWhenNoLabels();
|
||||
|
||||
final bool _hitTestSelf;
|
||||
@override
|
||||
bool hitTestSelf(Offset position) => true;
|
||||
bool hitTestSelf(Offset position) => _hitTestSelf;
|
||||
|
||||
@override
|
||||
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
|
||||
|
||||
@@ -1,23 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
/*
|
||||
* This file is part of PiliPlus
|
||||
*
|
||||
* PiliPlus is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PiliPlus is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with PiliPlus. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class Segment {
|
||||
import 'package:flutter/foundation.dart' show listEquals, kDebugMode;
|
||||
import 'package:flutter/gestures.dart' show TapGestureRecognizer;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart' show BoxHitTestEntry;
|
||||
|
||||
sealed class BaseSegment {
|
||||
final double start;
|
||||
final double end;
|
||||
final Color color;
|
||||
final String? title;
|
||||
final String? url;
|
||||
final int? from;
|
||||
final int? to;
|
||||
|
||||
Segment(
|
||||
this.start,
|
||||
this.end,
|
||||
this.color, [
|
||||
this.title,
|
||||
this.url,
|
||||
this.from,
|
||||
this.to,
|
||||
]);
|
||||
BaseSegment({
|
||||
required this.start,
|
||||
required this.end,
|
||||
});
|
||||
}
|
||||
|
||||
class Segment extends BaseSegment {
|
||||
final Color color;
|
||||
|
||||
Segment({
|
||||
required super.start,
|
||||
required super.end,
|
||||
required this.color,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
@@ -25,9 +45,38 @@ class Segment {
|
||||
return true;
|
||||
}
|
||||
if (other is Segment) {
|
||||
return start == other.start && end == other.end && color == other.color;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(start, end, color);
|
||||
}
|
||||
|
||||
class ViewPointSegment extends BaseSegment {
|
||||
final String? title;
|
||||
final String? url;
|
||||
final int? from;
|
||||
final int? to;
|
||||
|
||||
ViewPointSegment({
|
||||
required super.start,
|
||||
required super.end,
|
||||
this.title,
|
||||
this.url,
|
||||
this.from,
|
||||
this.to,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (other is ViewPointSegment) {
|
||||
return start == other.start &&
|
||||
end == other.end &&
|
||||
color == other.color &&
|
||||
title == other.title &&
|
||||
url == other.url &&
|
||||
from == other.from &&
|
||||
@@ -37,116 +86,284 @@ class Segment {
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(start, end, color, title, url, from, to);
|
||||
int get hashCode => Object.hash(start, end, title, url, from, to);
|
||||
}
|
||||
|
||||
class SegmentProgressBar extends CustomPainter {
|
||||
final List<Segment> segmentColors;
|
||||
double? _defHeight;
|
||||
|
||||
SegmentProgressBar({
|
||||
required this.segmentColors,
|
||||
class SegmentProgressBar extends BaseSegmentProgressBar<Segment> {
|
||||
const SegmentProgressBar({
|
||||
super.key,
|
||||
super.height = 3.5,
|
||||
required super.segments,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return RenderProgressBar(
|
||||
height: height,
|
||||
segments: segments,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RenderProgressBar extends BaseRenderProgressBar<Segment> {
|
||||
RenderProgressBar({
|
||||
required super.height,
|
||||
required super.segments,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
final size = this.size;
|
||||
final canvas = context.canvas;
|
||||
final paint = Paint()..style = PaintingStyle.fill;
|
||||
|
||||
for (int i = 0; i < segmentColors.length; i++) {
|
||||
final item = segmentColors[i];
|
||||
paint.color = item.color;
|
||||
for (final segment in segments) {
|
||||
paint.color = segment.color;
|
||||
final segmentStart = segment.start * size.width;
|
||||
final segmentEnd = segment.end * size.width;
|
||||
|
||||
if (segmentEnd > segmentStart ||
|
||||
(segmentEnd == segmentStart && segmentStart > 0)) {
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(
|
||||
segmentStart,
|
||||
0,
|
||||
segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
|
||||
size.height,
|
||||
),
|
||||
paint,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ViewPointSegmentProgressBar
|
||||
extends BaseSegmentProgressBar<ViewPointSegment> {
|
||||
const ViewPointSegmentProgressBar({
|
||||
super.key,
|
||||
super.height = 3.5,
|
||||
required super.segments,
|
||||
this.onSeek,
|
||||
});
|
||||
|
||||
final ValueSetter<Duration>? onSeek;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return RenderViewPointProgressBar(
|
||||
height: height,
|
||||
segments: segments,
|
||||
onSeek: onSeek,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
RenderViewPointProgressBar renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..height = height
|
||||
..segments = segments
|
||||
..onSeek = onSeek;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderViewPointProgressBar
|
||||
extends BaseRenderProgressBar<ViewPointSegment> {
|
||||
RenderViewPointProgressBar({
|
||||
required super.height,
|
||||
required super.segments,
|
||||
ValueSetter<Duration>? onSeek,
|
||||
}) : _onSeek = onSeek,
|
||||
_hitTestSelf = onSeek != null {
|
||||
if (onSeek != null) {
|
||||
_tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _onTapUp;
|
||||
}
|
||||
}
|
||||
|
||||
static const double barHeight = 15.0;
|
||||
|
||||
@override
|
||||
Size computeDryLayout(BoxConstraints constraints) {
|
||||
return constraints.constrain(Size(constraints.maxWidth, barHeight));
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
final size = this.size;
|
||||
final canvas = context.canvas;
|
||||
final paint = Paint()..style = PaintingStyle.fill;
|
||||
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(0, 0, size.width, barHeight),
|
||||
paint..color = Colors.grey[600]!.withValues(alpha: 0.45),
|
||||
);
|
||||
|
||||
paint.color = Colors.black.withValues(alpha: 0.5);
|
||||
|
||||
for (int index = 0; index < segments.length; index++) {
|
||||
final isFirst = index == 0;
|
||||
final item = segments[index];
|
||||
final segmentStart = item.start * size.width;
|
||||
final segmentEnd = item.end * size.width;
|
||||
|
||||
if (segmentEnd > segmentStart ||
|
||||
(segmentEnd == segmentStart && segmentStart > 0)) {
|
||||
if (item.title != null) {
|
||||
double fontSize = 10;
|
||||
double fontSize = 10;
|
||||
|
||||
_defHeight ??=
|
||||
(TextPainter(
|
||||
text: TextSpan(
|
||||
text: item.title,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout()).height +
|
||||
2;
|
||||
|
||||
TextPainter getTextPainter() => TextPainter(
|
||||
text: TextSpan(
|
||||
text: item.title,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: fontSize,
|
||||
height: 1,
|
||||
),
|
||||
TextPainter getTextPainter() => TextPainter(
|
||||
text: TextSpan(
|
||||
text: item.title,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: fontSize,
|
||||
height: 1,
|
||||
),
|
||||
strutStyle: StrutStyle(leading: 0, height: 1, fontSize: fontSize),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
),
|
||||
strutStyle: StrutStyle(leading: 0, height: 1, fontSize: fontSize),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
|
||||
TextPainter textPainter = getTextPainter();
|
||||
TextPainter textPainter = getTextPainter();
|
||||
|
||||
late double prevStart;
|
||||
if (i != 0) {
|
||||
prevStart = segmentColors[i - 1].start * size.width;
|
||||
}
|
||||
double width = i == 0 ? segmentStart : segmentStart - prevStart;
|
||||
|
||||
while (textPainter.width > width - 2 && fontSize >= 2) {
|
||||
fontSize -= 0.5;
|
||||
textPainter = getTextPainter();
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
canvas.drawRect(
|
||||
Rect.fromLTRB(
|
||||
0,
|
||||
-_defHeight!,
|
||||
size.width,
|
||||
0,
|
||||
),
|
||||
Paint()..color = Colors.grey[600]!.withValues(alpha: 0.45),
|
||||
);
|
||||
}
|
||||
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(
|
||||
segmentStart,
|
||||
-_defHeight!,
|
||||
segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
|
||||
size.height + _defHeight!,
|
||||
),
|
||||
paint,
|
||||
);
|
||||
|
||||
double textX = i == 0
|
||||
? (segmentStart - textPainter.width) / 2
|
||||
: (segmentStart - prevStart - textPainter.width) / 2 +
|
||||
prevStart +
|
||||
1;
|
||||
double textY = (-_defHeight! - textPainter.height) / 2;
|
||||
textPainter.paint(canvas, Offset(textX, textY));
|
||||
} else {
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(
|
||||
segmentStart,
|
||||
0,
|
||||
segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
|
||||
size.height,
|
||||
),
|
||||
paint,
|
||||
);
|
||||
late double prevStart;
|
||||
if (!isFirst) {
|
||||
prevStart = segments[index - 1].start * size.width;
|
||||
}
|
||||
final width = isFirst ? segmentStart : segmentStart - prevStart;
|
||||
|
||||
while (textPainter.width > width - 2 && fontSize >= 2) {
|
||||
fontSize -= 0.5;
|
||||
textPainter.dispose();
|
||||
textPainter = getTextPainter();
|
||||
}
|
||||
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(
|
||||
segmentStart,
|
||||
0,
|
||||
segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
|
||||
barHeight + height,
|
||||
),
|
||||
paint,
|
||||
);
|
||||
|
||||
final textX = isFirst
|
||||
? (segmentStart - textPainter.width) / 2
|
||||
: (segmentStart - prevStart - textPainter.width) / 2 +
|
||||
prevStart +
|
||||
1;
|
||||
final textY = (barHeight - textPainter.height) / 2;
|
||||
textPainter.paint(canvas, Offset(textX, textY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ValueSetter<Duration>? _onSeek;
|
||||
set onSeek(ValueSetter<Duration>? value) {
|
||||
if (_onSeek == value) {
|
||||
return;
|
||||
}
|
||||
_onSeek = value;
|
||||
}
|
||||
|
||||
TapGestureRecognizer? _tapGestureRecognizer;
|
||||
|
||||
@override
|
||||
bool shouldRepaint(SegmentProgressBar oldDelegate) {
|
||||
return segmentColors != oldDelegate.segmentColors;
|
||||
void dispose() {
|
||||
_onSeek = null;
|
||||
_tapGestureRecognizer
|
||||
?..onTapUp = null
|
||||
..dispose();
|
||||
_tapGestureRecognizer = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
final bool _hitTestSelf;
|
||||
@override
|
||||
bool hitTestSelf(Offset position) => _hitTestSelf;
|
||||
|
||||
@override
|
||||
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
|
||||
if (event is PointerDownEvent) {
|
||||
_tapGestureRecognizer?.addPointer(event);
|
||||
}
|
||||
}
|
||||
|
||||
void _onTapUp(TapUpDetails details) {
|
||||
try {
|
||||
final seg = details.localPosition.dx / size.width;
|
||||
final item = _segments
|
||||
.where((item) => item.start >= seg)
|
||||
.reduce((a, b) => a.start < b.start ? a : b);
|
||||
if (item.from case final from?) {
|
||||
_onSeek?.call(Duration(seconds: from));
|
||||
}
|
||||
// if (kDebugMode) debugPrint('${item.title},,${item.from}');
|
||||
} catch (e) {
|
||||
if (kDebugMode) rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BaseSegmentProgressBar<T extends BaseSegment>
|
||||
extends LeafRenderObjectWidget {
|
||||
const BaseSegmentProgressBar({
|
||||
super.key,
|
||||
this.height = 3.5,
|
||||
required this.segments,
|
||||
});
|
||||
|
||||
final double height;
|
||||
final List<T> segments;
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
BaseRenderProgressBar renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..height = height
|
||||
..segments = segments;
|
||||
}
|
||||
}
|
||||
|
||||
class BaseRenderProgressBar<T extends BaseSegment> extends RenderBox {
|
||||
BaseRenderProgressBar({
|
||||
required double height,
|
||||
required List<T> segments,
|
||||
ValueSetter<int>? onSeek,
|
||||
}) : _height = height,
|
||||
_segments = segments;
|
||||
|
||||
double _height;
|
||||
double get height => _height;
|
||||
set height(double value) {
|
||||
if (_height == value) return;
|
||||
_height = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
List<T> _segments;
|
||||
List<T> get segments => _segments;
|
||||
set segments(List<T> value) {
|
||||
if (listEquals(_segments, value)) return;
|
||||
_segments = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
size = computeDryLayout(constraints);
|
||||
}
|
||||
|
||||
@override
|
||||
Size computeDryLayout(BoxConstraints constraints) {
|
||||
return constraints.constrain(Size(constraints.maxWidth, height));
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isRepaintBoundary => true;
|
||||
}
|
||||
|
||||
@@ -838,7 +838,11 @@ class VideoDetailController extends GetxController
|
||||
segmentList.map((e) {
|
||||
double start = (e.segment.first / duration).clamp(0.0, 1.0);
|
||||
double end = (e.segment.second / duration).clamp(0.0, 1.0);
|
||||
return Segment(start, end, _getColor(e.segmentType));
|
||||
return Segment(
|
||||
start: start,
|
||||
end: end,
|
||||
color: _getColor(e.segmentType),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1453,7 +1457,7 @@ class VideoDetailController extends GetxController
|
||||
late final Map<int, String> vttSubtitles = {};
|
||||
late final RxInt vttSubtitlesIndex = (-1).obs;
|
||||
late final RxBool showVP = true.obs;
|
||||
late final RxList<Segment> viewPointList = <Segment>[].obs;
|
||||
late final RxList<ViewPointSegment> viewPointList = <ViewPointSegment>[].obs;
|
||||
|
||||
// 设定字幕轨道
|
||||
Future<void> setSubtitle(int index) async {
|
||||
@@ -1575,14 +1579,13 @@ class VideoDetailController extends GetxController
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
return Segment(
|
||||
start,
|
||||
start,
|
||||
Colors.black.withValues(alpha: 0.5),
|
||||
item.content,
|
||||
item.imgUrl,
|
||||
item.from,
|
||||
item.to,
|
||||
return ViewPointSegment(
|
||||
start: start,
|
||||
end: start,
|
||||
title: item.content,
|
||||
url: item.imgUrl,
|
||||
from: item.from,
|
||||
to: item.to,
|
||||
);
|
||||
}).toList();
|
||||
} catch (_) {}
|
||||
|
||||
@@ -100,7 +100,7 @@ class _ViewPointsPageState extends State<ViewPointsPage>
|
||||
),
|
||||
itemCount: videoDetailController.viewPointList.length,
|
||||
itemBuilder: (context, index) {
|
||||
Segment segment = videoDetailController.viewPointList[index];
|
||||
final segment = videoDetailController.viewPointList[index];
|
||||
if (currentIndex == -1 && segment.from != null && segment.to != null) {
|
||||
final positionSeconds =
|
||||
videoDetailController.plPlayerController.positionSeconds.value;
|
||||
@@ -122,7 +122,7 @@ class _ViewPointsPageState extends State<ViewPointsPage>
|
||||
return child;
|
||||
}
|
||||
|
||||
Widget _buildItem(ThemeData theme, Segment segment, bool isCurr) {
|
||||
Widget _buildItem(ThemeData theme, ViewPointSegment segment, bool isCurr) {
|
||||
final theme = Theme.of(context);
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
|
||||
@@ -1740,106 +1740,69 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
right: 0,
|
||||
child: Obx(
|
||||
() {
|
||||
if (plPlayerController.showControls.value) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
switch (plPlayerController.progressType) {
|
||||
case BtmProgressBehavior.onlyShowFullScreen:
|
||||
if (!isFullScreen) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
case BtmProgressBehavior.onlyHideFullScreen:
|
||||
if (isFullScreen) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
IgnorePointer(
|
||||
child: RepaintBoundary.wrap(
|
||||
Obx(() {
|
||||
final int value =
|
||||
plPlayerController.sliderPositionSeconds.value;
|
||||
final int max = plPlayerController
|
||||
.durationSeconds
|
||||
.value
|
||||
.inSeconds;
|
||||
final int buffer =
|
||||
plPlayerController.bufferedSeconds.value;
|
||||
return ProgressBar(
|
||||
progress: Duration(seconds: value),
|
||||
buffered: Duration(seconds: buffer),
|
||||
total: Duration(seconds: max),
|
||||
progressBarColor: primary,
|
||||
baseBarColor: const Color(0x33FFFFFF),
|
||||
bufferedBarColor: bufferedBarColor,
|
||||
thumbColor: primary,
|
||||
thumbGlowColor: thumbGlowColor,
|
||||
barHeight: 3.5,
|
||||
thumbRadius: 2.5,
|
||||
);
|
||||
}),
|
||||
0,
|
||||
),
|
||||
),
|
||||
if (plPlayerController.enableBlock &&
|
||||
videoDetailController.segmentProgressList.isNotEmpty)
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0.75,
|
||||
child: IgnorePointer(
|
||||
child: RepaintBoundary.wrap(
|
||||
CustomPaint(
|
||||
size: const Size(double.infinity, 3.5),
|
||||
painter: SegmentProgressBar(
|
||||
segmentColors:
|
||||
videoDetailController.segmentProgressList,
|
||||
),
|
||||
),
|
||||
1,
|
||||
final offstage = switch (plPlayerController.progressType) {
|
||||
BtmProgressBehavior.onlyShowFullScreen => !isFullScreen,
|
||||
BtmProgressBehavior.onlyHideFullScreen => isFullScreen,
|
||||
_ => plPlayerController.showControls.value,
|
||||
};
|
||||
return Offstage(
|
||||
offstage: offstage,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
Obx(() {
|
||||
final int value =
|
||||
plPlayerController.sliderPositionSeconds.value;
|
||||
final int max =
|
||||
plPlayerController.durationSeconds.value.inSeconds;
|
||||
final int buffer =
|
||||
plPlayerController.bufferedSeconds.value;
|
||||
return ProgressBar(
|
||||
progress: Duration(seconds: value),
|
||||
buffered: Duration(seconds: buffer),
|
||||
total: Duration(seconds: max),
|
||||
progressBarColor: primary,
|
||||
baseBarColor: const Color(0x33FFFFFF),
|
||||
bufferedBarColor: bufferedBarColor,
|
||||
thumbColor: primary,
|
||||
thumbGlowColor: thumbGlowColor,
|
||||
barHeight: 3.5,
|
||||
thumbRadius: 2.5,
|
||||
);
|
||||
}),
|
||||
if (plPlayerController.enableBlock &&
|
||||
videoDetailController.segmentProgressList.isNotEmpty)
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0.75,
|
||||
child: SegmentProgressBar(
|
||||
segments: videoDetailController.segmentProgressList,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (plPlayerController.showViewPoints &&
|
||||
videoDetailController.viewPointList.isNotEmpty &&
|
||||
videoDetailController.showVP.value) ...[
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0.75,
|
||||
child: IgnorePointer(
|
||||
child: RepaintBoundary.wrap(
|
||||
CustomPaint(
|
||||
size: const Size(double.infinity, 3.5),
|
||||
painter: SegmentProgressBar(
|
||||
segmentColors:
|
||||
videoDetailController.viewPointList,
|
||||
),
|
||||
),
|
||||
2,
|
||||
if (plPlayerController.showViewPoints &&
|
||||
videoDetailController.viewPointList.isNotEmpty &&
|
||||
videoDetailController.showVP.value)
|
||||
Padding(
|
||||
padding: const .only(bottom: 4.25),
|
||||
child: ViewPointSegmentProgressBar(
|
||||
segments: videoDetailController.viewPointList,
|
||||
onSeek: PlatformUtils.isMobile
|
||||
? (position) => plPlayerController.seekTo(
|
||||
position,
|
||||
isSeek: false,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (PlatformUtils.isMobile)
|
||||
buildViewPointWidget(
|
||||
videoDetailController,
|
||||
plPlayerController,
|
||||
4.25,
|
||||
maxWidth,
|
||||
),
|
||||
if (plPlayerController.showDmChart &&
|
||||
videoDetailController.showDmTrendChart.value)
|
||||
if (videoDetailController.dmTrend.value?.dataOrNull
|
||||
case final list?)
|
||||
buildDmChart(primary, list, videoDetailController),
|
||||
],
|
||||
if (plPlayerController.showDmChart &&
|
||||
videoDetailController.showDmTrendChart.value)
|
||||
if (videoDetailController.dmTrend.value?.dataOrNull
|
||||
case final list?)
|
||||
buildDmChart(primary, list, videoDetailController),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -2325,7 +2288,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
// fullscreen
|
||||
if (dx > maxWidth) {
|
||||
_removeDmAction();
|
||||
return const Positioned(left: 0, top: 0, child: SizedBox.shrink());
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final seekOffset = _getValidOffset(item.content.text);
|
||||
@@ -2346,7 +2309,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
|
||||
if (right > (maxWidth - item.xPosition)) {
|
||||
_removeDmAction();
|
||||
return const Positioned(left: 0, top: 0, child: SizedBox.shrink());
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final extra = item.content.extra;
|
||||
|
||||
@@ -22,9 +22,31 @@ class BottomControl extends StatelessWidget {
|
||||
final double maxWidth;
|
||||
final bool isFullScreen;
|
||||
final PlPlayerController controller;
|
||||
final Widget Function() buildBottomControl;
|
||||
final ValueGetter<Widget> buildBottomControl;
|
||||
final VideoDetailController videoDetailController;
|
||||
|
||||
void onDragStart(ThumbDragDetails duration) {
|
||||
feedBack();
|
||||
controller.onChangedSliderStart(duration.timeStamp);
|
||||
}
|
||||
|
||||
void onDragUpdate(ThumbDragDetails duration, int max) {
|
||||
if (!controller.isFileSource && controller.showSeekPreview) {
|
||||
controller.updatePreviewIndex(duration.timeStamp.inSeconds);
|
||||
}
|
||||
controller.onUpdatedSliderProgress(duration.timeStamp);
|
||||
}
|
||||
|
||||
void onSeek(Duration duration, int max) {
|
||||
if (controller.showSeekPreview) {
|
||||
controller.showPreview.value = false;
|
||||
}
|
||||
controller
|
||||
..onChangedSliderEnd()
|
||||
..onChangedSlider(duration.inSeconds.toDouble())
|
||||
..seekTo(Duration(seconds: duration.inSeconds), isSeek: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = ColorScheme.of(context);
|
||||
@@ -33,27 +55,6 @@ class BottomControl extends StatelessWidget {
|
||||
: colorScheme.primary;
|
||||
final thumbGlowColor = primary.withAlpha(80);
|
||||
final bufferedBarColor = primary.withValues(alpha: 0.4);
|
||||
void onDragStart(ThumbDragDetails duration) {
|
||||
feedBack();
|
||||
controller.onChangedSliderStart(duration.timeStamp);
|
||||
}
|
||||
|
||||
void onDragUpdate(ThumbDragDetails duration, int max) {
|
||||
if (!controller.isFileSource && controller.showSeekPreview) {
|
||||
controller.updatePreviewIndex(duration.timeStamp.inSeconds);
|
||||
}
|
||||
controller.onUpdatedSliderProgress(duration.timeStamp);
|
||||
}
|
||||
|
||||
void onSeek(Duration duration, int max) {
|
||||
if (controller.showSeekPreview) {
|
||||
controller.showPreview.value = false;
|
||||
}
|
||||
controller
|
||||
..onChangedSliderEnd()
|
||||
..onChangedSlider(duration.inSeconds.toDouble())
|
||||
..seekTo(Duration(seconds: duration.inSeconds), isSeek: false);
|
||||
}
|
||||
|
||||
Widget progressBar() {
|
||||
final child = Obx(() {
|
||||
@@ -93,63 +94,42 @@ class BottomControl extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 0, 10, 7),
|
||||
child: Obx(
|
||||
() => Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
progressBar(),
|
||||
if (controller.enableBlock &&
|
||||
videoDetailController.segmentProgressList.isNotEmpty)
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 5.25,
|
||||
child: IgnorePointer(
|
||||
child: RepaintBoundary(
|
||||
child: CustomPaint(
|
||||
key: const Key('segmentList'),
|
||||
size: const Size(double.infinity, 3.5),
|
||||
painter: SegmentProgressBar(
|
||||
segmentColors:
|
||||
videoDetailController.segmentProgressList,
|
||||
),
|
||||
),
|
||||
() => Offstage(
|
||||
offstage: !controller.showControls.value,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
progressBar(),
|
||||
if (controller.enableBlock &&
|
||||
videoDetailController.segmentProgressList.isNotEmpty)
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 5.25,
|
||||
child: SegmentProgressBar(
|
||||
segments: videoDetailController.segmentProgressList,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.showViewPoints &&
|
||||
videoDetailController.viewPointList.isNotEmpty &&
|
||||
videoDetailController.showVP.value) ...[
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 5.25,
|
||||
child: IgnorePointer(
|
||||
child: RepaintBoundary(
|
||||
child: CustomPaint(
|
||||
key: const Key('viewPointList'),
|
||||
size: const Size(double.infinity, 3.5),
|
||||
painter: SegmentProgressBar(
|
||||
segmentColors:
|
||||
videoDetailController.viewPointList,
|
||||
),
|
||||
),
|
||||
if (controller.showViewPoints &&
|
||||
videoDetailController.viewPointList.isNotEmpty &&
|
||||
videoDetailController.showVP.value)
|
||||
Padding(
|
||||
padding: const .only(bottom: 8.75),
|
||||
child: ViewPointSegmentProgressBar(
|
||||
segments: videoDetailController.viewPointList,
|
||||
onSeek: PlatformUtils.isDesktop
|
||||
? (position) =>
|
||||
controller.seekTo(position, isSeek: false)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!PlatformUtils.isMobile)
|
||||
buildViewPointWidget(
|
||||
videoDetailController,
|
||||
controller,
|
||||
8.75,
|
||||
maxWidth - 40,
|
||||
),
|
||||
if (videoDetailController.showDmTrendChart.value)
|
||||
if (videoDetailController.dmTrend.value?.dataOrNull
|
||||
case final list?)
|
||||
buildDmChart(primary, list, videoDetailController, 4.5),
|
||||
],
|
||||
if (videoDetailController.showDmTrendChart.value)
|
||||
if (videoDetailController.dmTrend.value?.dataOrNull
|
||||
case final list?)
|
||||
buildDmChart(primary, list, videoDetailController, 4.5),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user