mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
refa player (#1848)
* tweak
* Reapply "opt: audio uri" (#1833)
This reverts commit 8e726f49b2.
* opt: player
* opt: player
* refa: create player
* refa: player
* opt: UaType
* fix: sb seek preview
* opt: setSub
* fix: screenshot
* opt: unnecessary final player state
* opt: player track
* opt FileSource constructor [skip ci]
* fixes
* fix: dispose player
* fix: quote
* update
* fix: multi ua & remove unused loop
* remove unneeded check [skip ci]
---------
Co-authored-by: dom <githubaccount56556@proton.me>
This commit is contained in:
committed by
GitHub
parent
6782bee11a
commit
7276cde48a
2439
lib/plugin/pl_player/view/view.dart
Normal file
2439
lib/plugin/pl_player/view/view.dart
Normal file
File diff suppressed because it is too large
Load Diff
503
lib/plugin/pl_player/view/widgets.dart
Normal file
503
lib/plugin/pl_player/view/widgets.dart
Normal file
@@ -0,0 +1,503 @@
|
||||
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,
|
||||
double maxHeight,
|
||||
ValueGetter<bool> isMounted,
|
||||
) {
|
||||
return Obx(
|
||||
() {
|
||||
if (!plPlayerController.showPreview.value) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
try {
|
||||
final data = plPlayerController.videoShot!.data;
|
||||
|
||||
final double scale =
|
||||
plPlayerController.isFullScreen.value &&
|
||||
(PlatformUtils.isDesktop || !plPlayerController.isVertical)
|
||||
? 4
|
||||
: 3;
|
||||
double height = 27 * scale;
|
||||
final compatHeight = maxHeight - 140;
|
||||
if (compatHeight > 50) {
|
||||
height = math.min(height, compatHeight);
|
||||
}
|
||||
|
||||
final int imgXLen = data.imgXLen;
|
||||
final int imgYLen = data.imgYLen;
|
||||
final int totalPerImage = data.totalPerImage;
|
||||
double imgXSize = data.imgXSize;
|
||||
double imgYSize = data.imgYSize;
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: Obx(
|
||||
() {
|
||||
final index = plPlayerController.previewIndex.value!;
|
||||
int pageIndex = (index ~/ totalPerImage).clamp(
|
||||
0,
|
||||
data.image.length - 1,
|
||||
);
|
||||
int align = index % totalPerImage;
|
||||
int x = align % imgXLen;
|
||||
int y = align ~/ imgYLen;
|
||||
final url = data.image[pageIndex];
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: StyleString.mdRadius,
|
||||
child: VideoShotImage(
|
||||
url: url,
|
||||
x: x,
|
||||
y: y,
|
||||
imgXSize: imgXSize,
|
||||
imgYSize: imgYSize,
|
||||
height: height,
|
||||
imageCache: plPlayerController.previewCache,
|
||||
onSetSize: (xSize, ySize) => data
|
||||
..imgXSize = imgXSize = xSize
|
||||
..imgYSize = imgYSize = ySize,
|
||||
isMounted: isMounted,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
if (kDebugMode) rethrow;
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class VideoShotImage extends StatefulWidget {
|
||||
const VideoShotImage({
|
||||
super.key,
|
||||
required this.imageCache,
|
||||
required this.url,
|
||||
required this.x,
|
||||
required this.y,
|
||||
required this.imgXSize,
|
||||
required this.imgYSize,
|
||||
required this.height,
|
||||
required this.onSetSize,
|
||||
required this.isMounted,
|
||||
});
|
||||
|
||||
final Map<String, ui.Image?> imageCache;
|
||||
final String url;
|
||||
final int x;
|
||||
final int y;
|
||||
final double imgXSize;
|
||||
final double imgYSize;
|
||||
final double height;
|
||||
final Function(double imgXSize, double imgYSize) onSetSize;
|
||||
final ValueGetter<bool> isMounted;
|
||||
|
||||
@override
|
||||
State<VideoShotImage> createState() => _VideoShotImageState();
|
||||
}
|
||||
|
||||
Future<ui.Image?> _getImg(String url) async {
|
||||
final cacheManager = DefaultCacheManager();
|
||||
final cacheKey = Utils.getFileName(url, fileExt: false);
|
||||
try {
|
||||
final fileInfo = await cacheManager.getSingleFile(
|
||||
ImageUtils.safeThumbnailUrl(url),
|
||||
key: cacheKey,
|
||||
headers: Constants.baseHeaders,
|
||||
);
|
||||
return _loadImg(fileInfo.path);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<ui.Image?> _loadImg(String path) async {
|
||||
final codec = await ui.instantiateImageCodecFromBuffer(
|
||||
await ImmutableBuffer.fromFilePath(path),
|
||||
);
|
||||
final frame = await codec.getNextFrame();
|
||||
codec.dispose();
|
||||
return frame.image;
|
||||
}
|
||||
|
||||
class _VideoShotImageState extends State<VideoShotImage> {
|
||||
late Size _size;
|
||||
late Rect _srcRect;
|
||||
late Rect _dstRect;
|
||||
late RRect _rrect;
|
||||
ui.Image? _image;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initSize();
|
||||
_loadImg();
|
||||
}
|
||||
|
||||
void _initSizeIfNeeded() {
|
||||
if (_size.width.isNaN) {
|
||||
_initSize();
|
||||
}
|
||||
}
|
||||
|
||||
void _initSize() {
|
||||
if (widget.imgXSize == 0) {
|
||||
if (_image != null) {
|
||||
final imgXSize = _image!.width / 10;
|
||||
final imgYSize = _image!.height / 10;
|
||||
final height = widget.height;
|
||||
final width = height * imgXSize / imgYSize;
|
||||
_setRect(width, height);
|
||||
_setSrcRect(imgXSize, imgYSize);
|
||||
widget.onSetSize(imgXSize, imgYSize);
|
||||
} else {
|
||||
_setRect(double.nan, double.nan);
|
||||
_setSrcRect(widget.imgXSize, widget.imgYSize);
|
||||
}
|
||||
} else {
|
||||
final height = widget.height;
|
||||
final width = height * widget.imgXSize / widget.imgYSize;
|
||||
_setRect(width, height);
|
||||
_setSrcRect(widget.imgXSize, widget.imgYSize);
|
||||
}
|
||||
}
|
||||
|
||||
void _setRect(double width, double height) {
|
||||
_size = Size(width, height);
|
||||
_dstRect = Rect.fromLTRB(0, 0, width, height);
|
||||
_rrect = RRect.fromRectAndRadius(_dstRect, const Radius.circular(10));
|
||||
}
|
||||
|
||||
void _setSrcRect(double imgXSize, double imgYSize) {
|
||||
_srcRect = Rect.fromLTWH(
|
||||
widget.x * imgXSize,
|
||||
widget.y * imgYSize,
|
||||
imgXSize,
|
||||
imgYSize,
|
||||
);
|
||||
}
|
||||
|
||||
void _loadImg() {
|
||||
final url = widget.url;
|
||||
_image = widget.imageCache[url];
|
||||
if (_image != null) {
|
||||
_initSizeIfNeeded();
|
||||
} else if (!widget.imageCache.containsKey(url)) {
|
||||
widget.imageCache[url] = null;
|
||||
_getImg(url).then((image) {
|
||||
if (image != null) {
|
||||
if (widget.isMounted()) {
|
||||
widget.imageCache[url] = image;
|
||||
}
|
||||
if (mounted) {
|
||||
_image = image;
|
||||
_initSizeIfNeeded();
|
||||
setState(() {});
|
||||
}
|
||||
} else {
|
||||
widget.imageCache.remove(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(VideoShotImage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.url != widget.url) {
|
||||
_loadImg();
|
||||
}
|
||||
if (oldWidget.x != widget.x || oldWidget.y != widget.y) {
|
||||
_setSrcRect(widget.imgXSize, widget.imgYSize);
|
||||
}
|
||||
}
|
||||
|
||||
late final _imgPaint = Paint()..filterQuality = FilterQuality.medium;
|
||||
late final _borderPaint = Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_image != null) {
|
||||
return CroppedImage(
|
||||
size: _size,
|
||||
image: _image!,
|
||||
srcRect: _srcRect,
|
||||
dstRect: _dstRect,
|
||||
rrect: _rrect,
|
||||
imgPaint: _imgPaint,
|
||||
borderPaint: _borderPaint,
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
required this.duration,
|
||||
});
|
||||
|
||||
final String position;
|
||||
final String duration;
|
||||
|
||||
@override
|
||||
_RenderVideoTime createRenderObject(BuildContext context) => _RenderVideoTime(
|
||||
position: position,
|
||||
duration: duration,
|
||||
);
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
covariant _RenderVideoTime renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..position = position
|
||||
..duration = duration;
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderVideoTime extends RenderBox {
|
||||
_RenderVideoTime({
|
||||
required String position,
|
||||
required String duration,
|
||||
}) : _position = position,
|
||||
_duration = duration;
|
||||
|
||||
String _duration;
|
||||
set duration(String value) {
|
||||
_duration = value;
|
||||
final paragraph = _buildParagraph(const Color(0xFFD0D0D0), _duration);
|
||||
if (paragraph.maxIntrinsicWidth != _cache?.maxIntrinsicWidth) {
|
||||
markNeedsLayout();
|
||||
}
|
||||
_cache?.dispose();
|
||||
_cache = paragraph;
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
String _position;
|
||||
set position(String value) {
|
||||
_position = value;
|
||||
markNeedsPaint();
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
ui.Paragraph? _cache;
|
||||
|
||||
ui.Paragraph _buildParagraph(Color color, String time) {
|
||||
final builder =
|
||||
ui.ParagraphBuilder(
|
||||
ui.ParagraphStyle(
|
||||
fontSize: 10,
|
||||
height: 1.4,
|
||||
fontFamily: 'Monospace',
|
||||
),
|
||||
)
|
||||
..pushStyle(
|
||||
ui.TextStyle(
|
||||
color: color,
|
||||
fontSize: 10,
|
||||
height: 1.4,
|
||||
fontFamily: 'Monospace',
|
||||
fontFeatures: const [FontFeature.tabularFigures()],
|
||||
),
|
||||
)
|
||||
..addText(time);
|
||||
return builder.build()
|
||||
..layout(const ui.ParagraphConstraints(width: .infinity));
|
||||
}
|
||||
|
||||
@override
|
||||
ui.Size computeDryLayout(covariant BoxConstraints constraints) {
|
||||
final paragraph = _cache ??= _buildParagraph(
|
||||
const Color(0xFFD0D0D0),
|
||||
_duration,
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, ui.Offset offset) {
|
||||
final para = _buildParagraph(Colors.white, _position);
|
||||
context.canvas
|
||||
..drawParagraph(
|
||||
para,
|
||||
Offset(
|
||||
offset.dx + _cache!.maxIntrinsicWidth - para.maxIntrinsicWidth,
|
||||
offset.dy,
|
||||
),
|
||||
)
|
||||
..drawParagraph(_cache!, Offset(offset.dx, offset.dy + para.height));
|
||||
para.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_cache?.dispose();
|
||||
_cache = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isRepaintBoundary => true;
|
||||
}
|
||||
Reference in New Issue
Block a user