Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-05-06 14:14:19 +08:00
parent 1a8c348af1
commit 07843a5e77
239 changed files with 3175 additions and 13237 deletions

View File

@@ -0,0 +1,89 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:media_kit_video/media_kit_video.dart';
class SimpleVideoTexture extends StatefulWidget {
final Color fill;
final VideoController controller;
final double? aspectRatio;
final FilterQuality filterQuality;
const SimpleVideoTexture({
super.key,
this.fill = Colors.black,
required this.controller,
this.aspectRatio,
this.filterQuality = FilterQuality.low,
});
@override
State<SimpleVideoTexture> createState() => SimpleVideoTextureState();
}
class SimpleVideoTextureState extends State<SimpleVideoTexture> {
late double _devicePixelRatio;
late bool _visible =
widget.controller.player.state.width > 0 &&
widget.controller.player.state.height > 0;
late final StreamSubscription<(int, int)> _subscription;
@override
void initState() {
super.initState();
// --------------------------------------------------
// Do not show the video frame until width & height are available.
// Since [ValueNotifier<Rect?>] inside [VideoController] only gets updated by the render loop (i.e. it will not fire when video's width & height are not available etc.), it's important to handle this separately here.
_subscription = widget.controller.player.stream.size.listen((value) {
final visible = value.$1 > 0 && value.$2 > 0;
if (_visible != visible) {
_visible = visible;
// ignore: invalid_use_of_visible_for_testing_member, invalid_use_of_protected_member
widget.controller.rect.notifyListeners();
}
});
}
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_devicePixelRatio = MediaQuery.devicePixelRatioOf(context);
}
@override
Widget build(BuildContext context) {
final ctr = widget.controller;
return ListenableBuilder(
listenable: Listenable.merge([ctr.id, ctr.rect]),
builder: (context, _) {
final id = ctr.id.value;
final rect = ctr.rect.value;
if (id != null && rect != null && _visible) {
return SizedBox(
width: widget.aspectRatio == null
? rect.width / _devicePixelRatio
: rect.height / _devicePixelRatio * widget.aspectRatio!,
height: rect.height / _devicePixelRatio,
child: Stack(
children: [
if (rect.width <= 1.0 &&
rect.height <= 1.0 &&
widget.fill != Colors.transparent)
Positioned.fill(child: ColoredBox(color: widget.fill)),
Texture(textureId: id, filterQuality: widget.filterQuality),
],
),
);
}
return const SizedBox();
},
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,56 +1,5 @@
part of 'view.dart';
Widget buildDmChart(
Color color,
List<double> dmTrend,
VideoDetailController videoDetailController, [
double offset = 0,
]) {
return IgnorePointer(
child: Container(
height: 12,
margin: EdgeInsets.only(
bottom:
videoDetailController.viewPointList.isNotEmpty &&
videoDetailController.showVP.value
? 19.25 + offset
: 4.25 + offset,
),
child: LineChart(
LineChartData(
titlesData: const FlTitlesData(show: false),
lineTouchData: const LineTouchData(enabled: false),
gridData: const FlGridData(show: false),
borderData: FlBorderData(show: false),
minX: 0,
maxX: (dmTrend.length - 1).toDouble(),
minY: 0,
maxY: dmTrend.max,
lineBarsData: [
LineChartBarData(
spots: List.generate(
dmTrend.length,
(index) => FlSpot(
index.toDouble(),
dmTrend[index],
),
),
isCurved: true,
barWidth: 1,
color: color,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(
show: true,
color: color.withValues(alpha: 0.4),
),
),
],
),
),
),
);
}
Widget buildSeekPreviewWidget(
PlPlayerController plPlayerController,
double maxWidth,
@@ -291,94 +240,6 @@ class _VideoShotImageState extends State<VideoShotImage> {
}
}
const double _triangleHeight = 5.6;
class _DanmakuTip extends SingleChildRenderObjectWidget {
const _DanmakuTip({
this.offset = 0,
super.child,
});
final double offset;
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderDanmakuTip(offset: offset);
}
@override
void updateRenderObject(
BuildContext context,
_RenderDanmakuTip renderObject,
) {
renderObject.offset = offset;
}
}
class _RenderDanmakuTip extends RenderProxyBox {
_RenderDanmakuTip({
required double offset,
}) : _offset = offset;
double _offset;
double get offset => _offset;
set offset(double value) {
if (_offset == value) return;
_offset = value;
markNeedsPaint();
}
@override
void paint(PaintingContext context, Offset offset) {
final paint = Paint()
..color = const Color(0xB3000000)
..style = .fill;
final radius = size.height / 2;
const triangleBase = _triangleHeight * 2 / 3;
final triangleCenterX = (size.width / 2 + _offset).clamp(
radius + triangleBase,
size.width - radius - triangleBase,
);
final path = Path()
// triangle (exceed)
..moveTo(triangleCenterX - triangleBase, 0)
..lineTo(triangleCenterX, -_triangleHeight)
..lineTo(triangleCenterX + triangleBase, 0)
// top
..lineTo(size.width - radius, 0)
// right
..arcToPoint(
Offset(size.width - radius, size.height),
radius: Radius.circular(radius),
)
// bottom
..lineTo(radius, size.height)
// left
..arcToPoint(
Offset(radius, 0),
radius: Radius.circular(radius),
)
..close();
context.canvas
..save()
..translate(offset.dx, offset.dy)
..drawPath(path, paint)
..drawPath(
path,
paint
..color = const Color(0x7EFFFFFF)
..style = PaintingStyle.stroke
..strokeWidth = 1.25,
)
..restore();
super.paint(context, offset);
}
}
class _VideoTime extends LeafRenderObjectWidget {
const _VideoTime({
required this.position,
@@ -465,12 +326,6 @@ class _RenderVideoTime extends RenderBox {
return Size(paragraph.maxIntrinsicWidth, paragraph.height * 2);
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.label = 'position:$_position\nduration:$_duration';
}
@override
void performLayout() {
size = computeDryLayout(constraints);