refa: video (#1555)

* refa: video [skip ci]

* fix: scroll [skip ci]

* mod: only left click

* downgrade

* refa: background play & wakelock [skip ci]

* fix: subtitle [skip ci]

* upgrade deps

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* mod: long press

* tweak

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* fix [skip ci]

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* use right pos

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* delay showing

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* fix: null danmaku

* remove

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
My-Responsitories
2025-10-14 17:05:31 +08:00
committed by GitHub
parent 4cf1c25b36
commit 9d747c8e2c
15 changed files with 1071 additions and 313 deletions

2
.fvmrc
View File

@@ -1,3 +1,3 @@
{
"flutter": "3.35.5"
}
}

View File

@@ -0,0 +1,905 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' show Platform;
import 'dart:math' as math;
import 'package:flutter/foundation.dart' show clampDouble;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:vector_math/vector_math_64.dart' show Quad, Vector3;
class MouseInteractiveViewer extends StatefulWidget {
const MouseInteractiveViewer({
super.key,
this.clipBehavior = Clip.hardEdge,
this.panAxis = PanAxis.free,
this.boundaryMargin = EdgeInsets.zero,
this.constrained = true,
this.maxScale = 2.5,
this.minScale = 0.8,
this.interactionEndFrictionCoefficient = _kDrag,
this.pointerSignalFallback,
this.onPointerPanZoomUpdate,
this.onPointerPanZoomEnd,
this.onPointerDown,
this.onInteractionEnd,
this.onInteractionStart,
this.onInteractionUpdate,
this.panEnabled = true,
this.scaleEnabled = true,
this.scaleFactor = kDefaultMouseScrollToScaleFactor,
this.transformationController,
this.alignment,
this.trackpadScrollCausesScale = false,
required this.childKey,
required this.child,
}) : assert(minScale > 0),
assert(interactionEndFrictionCoefficient > 0),
assert(maxScale > 0),
assert(maxScale >= minScale);
final Alignment? alignment;
final Clip clipBehavior;
final PanAxis panAxis;
final EdgeInsets boundaryMargin;
final Widget child;
final bool constrained;
final bool panEnabled;
final bool scaleEnabled;
final bool trackpadScrollCausesScale;
final double scaleFactor;
final double maxScale;
final double minScale;
final double interactionEndFrictionCoefficient;
final PointerSignalEventListener? pointerSignalFallback;
final PointerPanZoomUpdateEventListener? onPointerPanZoomUpdate;
final PointerPanZoomEndEventListener? onPointerPanZoomEnd;
final PointerDownEventListener? onPointerDown;
final GestureScaleEndCallback? onInteractionEnd;
final GestureScaleStartCallback? onInteractionStart;
final GestureScaleUpdateCallback? onInteractionUpdate;
final TransformationController? transformationController;
final GlobalKey childKey;
static const double _kDrag = 0.0000135;
@override
State<MouseInteractiveViewer> createState() => _MouseInteractiveViewerState();
}
class _MouseInteractiveViewerState extends State<MouseInteractiveViewer>
with TickerProviderStateMixin {
late TransformationController _transformer =
widget.transformationController ?? TransformationController();
final GlobalKey _parentKey = GlobalKey();
Animation<Offset>? _animation;
Animation<double>? _scaleAnimation;
late Offset _scaleAnimationFocalPoint;
late AnimationController _controller;
late AnimationController _scaleController;
Axis? _currentAxis;
Offset? _referenceFocalPoint;
double? _scaleStart;
double? _rotationStart = 0.0;
double _currentRotation = 0.0;
_GestureType? _gestureType;
static final gestureSettings = DeviceGestureSettings(
touchSlop: Platform.isIOS ? 9 : 4,
);
late final _scaleGestureRecognizer =
ScaleGestureRecognizer(
debugOwner: this,
allowedButtonsFilter: (buttons) => buttons == kPrimaryButton,
trackpadScrollToScaleFactor: Offset(0, -1 / widget.scaleFactor),
trackpadScrollCausesScale: widget.trackpadScrollCausesScale,
)
..gestureSettings = gestureSettings
..onStart = _onScaleStart
..onUpdate = _onScaleUpdate
..onEnd = _onScaleEnd;
final bool _rotateEnabled = false;
Rect get _boundaryRect {
assert(widget.childKey.currentContext != null);
final RenderBox childRenderBox =
widget.childKey.currentContext!.findRenderObject()! as RenderBox;
final Size childSize = childRenderBox.size;
final Rect boundaryRect = widget.boundaryMargin.inflateRect(
Offset.zero & childSize,
);
assert(
!boundaryRect.isEmpty,
"InteractiveViewer's child must have nonzero dimensions.",
);
assert(
boundaryRect.isFinite ||
(boundaryRect.left.isInfinite &&
boundaryRect.top.isInfinite &&
boundaryRect.right.isInfinite &&
boundaryRect.bottom.isInfinite),
'boundaryRect must either be infinite in all directions or finite in all directions.',
);
return boundaryRect;
}
Rect get _viewport {
assert(_parentKey.currentContext != null);
final RenderBox parentRenderBox =
_parentKey.currentContext!.findRenderObject()! as RenderBox;
return Offset.zero & parentRenderBox.size;
}
Matrix4 _matrixTranslate(Matrix4 matrix, Offset translation) {
if (translation == Offset.zero) {
return matrix.clone();
}
final Offset alignedTranslation;
if (_currentAxis != null) {
alignedTranslation = switch (widget.panAxis) {
PanAxis.horizontal => _alignAxis(translation, Axis.horizontal),
PanAxis.vertical => _alignAxis(translation, Axis.vertical),
PanAxis.aligned => _alignAxis(translation, _currentAxis!),
PanAxis.free => translation,
};
} else {
alignedTranslation = translation;
}
final Matrix4 nextMatrix = matrix.clone()
..translateByDouble(alignedTranslation.dx, alignedTranslation.dy, 0, 1);
final Quad nextViewport = _transformViewport(nextMatrix, _viewport);
if (_boundaryRect.isInfinite) {
return nextMatrix;
}
final Quad boundariesAabbQuad = _getAxisAlignedBoundingBoxWithRotation(
_boundaryRect,
_currentRotation,
);
final Offset offendingDistance = _exceedsBy(
boundariesAabbQuad,
nextViewport,
);
if (offendingDistance == Offset.zero) {
return nextMatrix;
}
final Offset nextTotalTranslation = _getMatrixTranslation(nextMatrix);
final double currentScale = matrix.getMaxScaleOnAxis();
final Offset correctedTotalTranslation = Offset(
nextTotalTranslation.dx - offendingDistance.dx * currentScale,
nextTotalTranslation.dy - offendingDistance.dy * currentScale,
);
final Matrix4 correctedMatrix = matrix.clone()
..setTranslation(
Vector3(
correctedTotalTranslation.dx,
correctedTotalTranslation.dy,
0.0,
),
);
final Quad correctedViewport = _transformViewport(
correctedMatrix,
_viewport,
);
final Offset offendingCorrectedDistance = _exceedsBy(
boundariesAabbQuad,
correctedViewport,
);
if (offendingCorrectedDistance == Offset.zero) {
return correctedMatrix;
}
if (offendingCorrectedDistance.dx != 0.0 &&
offendingCorrectedDistance.dy != 0.0) {
return matrix.clone();
}
final Offset unidirectionalCorrectedTotalTranslation = Offset(
offendingCorrectedDistance.dx == 0.0 ? correctedTotalTranslation.dx : 0.0,
offendingCorrectedDistance.dy == 0.0 ? correctedTotalTranslation.dy : 0.0,
);
return matrix.clone()..setTranslation(
Vector3(
unidirectionalCorrectedTotalTranslation.dx,
unidirectionalCorrectedTotalTranslation.dy,
0.0,
),
);
}
Matrix4 _matrixScale(Matrix4 matrix, double scale) {
if (scale == 1.0) {
return matrix.clone();
}
assert(scale != 0.0);
final double currentScale = _transformer.value.getMaxScaleOnAxis();
final double totalScale = math.max(
currentScale * scale,
math.max(
_viewport.width / _boundaryRect.width,
_viewport.height / _boundaryRect.height,
),
);
final double clampedTotalScale = clampDouble(
totalScale,
widget.minScale,
widget.maxScale,
);
final double clampedScale = clampedTotalScale / currentScale;
return matrix.clone()
..scaleByDouble(clampedScale, clampedScale, clampedScale, 1);
}
Matrix4 _matrixRotate(Matrix4 matrix, double rotation, Offset focalPoint) {
if (rotation == 0) {
return matrix.clone();
}
final Offset focalPointScene = _transformer.toScene(focalPoint);
return matrix.clone()
..translateByDouble(focalPointScene.dx, focalPointScene.dy, 0, 1)
..rotateZ(-rotation)
..translateByDouble(-focalPointScene.dx, -focalPointScene.dy, 0, 1);
}
bool _gestureIsSupported(_GestureType? gestureType) {
return switch (gestureType) {
_GestureType.rotate => _rotateEnabled,
_GestureType.scale => widget.scaleEnabled,
_GestureType.pan || null => widget.panEnabled,
};
}
_GestureType _getGestureType(ScaleUpdateDetails details) {
final double scale = !widget.scaleEnabled ? 1.0 : details.scale;
final double rotation = !_rotateEnabled ? 0.0 : details.rotation;
if ((scale - 1).abs() > rotation.abs()) {
return _GestureType.scale;
} else if (rotation != 0.0) {
return _GestureType.rotate;
} else {
return _GestureType.pan;
}
}
// Handle the start of a gesture. All of pan, scale, and rotate are handled
// with GestureDetector's scale gesture.
void _onScaleStart(ScaleStartDetails details) {
widget.onInteractionStart?.call(details);
if (_controller.isAnimating) {
_controller
..stop()
..reset();
_animation?.removeListener(_handleInertiaAnimation);
_animation = null;
}
if (_scaleController.isAnimating) {
_scaleController
..stop()
..reset();
_scaleAnimation?.removeListener(_handleScaleAnimation);
_scaleAnimation = null;
}
_gestureType = null;
_currentAxis = null;
_scaleStart = _transformer.value.getMaxScaleOnAxis();
_referenceFocalPoint = _transformer.toScene(details.localFocalPoint);
_rotationStart = _currentRotation;
}
// Handle an update to an ongoing gesture. All of pan, scale, and rotate are
// handled with GestureDetector's scale gesture.
void _onScaleUpdate(ScaleUpdateDetails details) {
final double scale = _transformer.value.getMaxScaleOnAxis();
_scaleAnimationFocalPoint = details.localFocalPoint;
final Offset focalPointScene = _transformer.toScene(
details.localFocalPoint,
);
if (_gestureType == _GestureType.pan) {
// When a gesture first starts, it sometimes has no change in scale and
// rotation despite being a two-finger gesture. Here the gesture is
// allowed to be reinterpreted as its correct type after originally
// being marked as a pan.
_gestureType = _getGestureType(details);
} else {
_gestureType ??= _getGestureType(details);
}
if (!_gestureIsSupported(_gestureType)) {
widget.onInteractionUpdate?.call(details);
return;
}
switch (_gestureType!) {
case _GestureType.scale:
assert(_scaleStart != null);
// details.scale gives us the amount to change the scale as of the
// start of this gesture, so calculate the amount to scale as of the
// previous call to _onScaleUpdate.
final double desiredScale = _scaleStart! * details.scale;
final double scaleChange = desiredScale / scale;
_transformer.value = _matrixScale(_transformer.value, scaleChange);
// While scaling, translate such that the user's two fingers stay on
// the same places in the scene. That means that the focal point of
// the scale should be on the same place in the scene before and after
// the scale.
final Offset focalPointSceneScaled = _transformer.toScene(
details.localFocalPoint,
);
_transformer.value = _matrixTranslate(
_transformer.value,
focalPointSceneScaled - _referenceFocalPoint!,
);
// details.localFocalPoint should now be at the same location as the
// original _referenceFocalPoint point. If it's not, that's because
// the translate came in contact with a boundary. In that case, update
// _referenceFocalPoint so subsequent updates happen in relation to
// the new effective focal point.
final Offset focalPointSceneCheck = _transformer.toScene(
details.localFocalPoint,
);
if (_round(_referenceFocalPoint!) != _round(focalPointSceneCheck)) {
_referenceFocalPoint = focalPointSceneCheck;
}
case _GestureType.rotate:
if (details.rotation == 0.0) {
widget.onInteractionUpdate?.call(details);
return;
}
final double desiredRotation = _rotationStart! + details.rotation;
_transformer.value = _matrixRotate(
_transformer.value,
_currentRotation - desiredRotation,
details.localFocalPoint,
);
_currentRotation = desiredRotation;
case _GestureType.pan:
assert(_referenceFocalPoint != null);
// details may have a change in scale here when scaleEnabled is false.
// In an effort to keep the behavior similar whether or not scaleEnabled
// is true, these gestures are thrown away.
if (details.scale != 1.0) {
widget.onInteractionUpdate?.call(details);
return;
}
_currentAxis ??= _getPanAxis(_referenceFocalPoint!, focalPointScene);
// Translate so that the same point in the scene is underneath the
// focal point before and after the movement.
final Offset translationChange =
focalPointScene - _referenceFocalPoint!;
_transformer.value = _matrixTranslate(
_transformer.value,
translationChange,
);
_referenceFocalPoint = _transformer.toScene(details.localFocalPoint);
}
widget.onInteractionUpdate?.call(details);
}
// Handle the end of a gesture of _GestureType. All of pan, scale, and rotate
// are handled with GestureDetector's scale gesture.
void _onScaleEnd(ScaleEndDetails details) {
widget.onInteractionEnd?.call(details);
_scaleStart = null;
_rotationStart = null;
_referenceFocalPoint = null;
_animation?.removeListener(_handleInertiaAnimation);
_scaleAnimation?.removeListener(_handleScaleAnimation);
_controller.reset();
_scaleController.reset();
if (!_gestureIsSupported(_gestureType)) {
_currentAxis = null;
return;
}
switch (_gestureType) {
case _GestureType.pan:
if (details.velocity.pixelsPerSecond.distance < kMinFlingVelocity) {
_currentAxis = null;
return;
}
final Vector3 translationVector = _transformer.value.getTranslation();
final Offset translation = Offset(
translationVector.x,
translationVector.y,
);
final FrictionSimulation frictionSimulationX = FrictionSimulation(
widget.interactionEndFrictionCoefficient,
translation.dx,
details.velocity.pixelsPerSecond.dx,
);
final FrictionSimulation frictionSimulationY = FrictionSimulation(
widget.interactionEndFrictionCoefficient,
translation.dy,
details.velocity.pixelsPerSecond.dy,
);
final double tFinal = _getFinalTime(
details.velocity.pixelsPerSecond.distance,
widget.interactionEndFrictionCoefficient,
);
_animation =
Tween<Offset>(
begin: translation,
end: Offset(
frictionSimulationX.finalX,
frictionSimulationY.finalX,
),
).animate(
CurvedAnimation(parent: _controller, curve: Curves.decelerate),
)
..addListener(_handleInertiaAnimation);
_controller
..duration = Duration(milliseconds: (tFinal * 1000).round())
..forward();
case _GestureType.scale:
if (details.scaleVelocity.abs() < 0.1) {
_currentAxis = null;
return;
}
final double scale = _transformer.value.getMaxScaleOnAxis();
final FrictionSimulation frictionSimulation = FrictionSimulation(
widget.interactionEndFrictionCoefficient * widget.scaleFactor,
scale,
details.scaleVelocity / 10,
);
final double tFinal = _getFinalTime(
details.scaleVelocity.abs(),
widget.interactionEndFrictionCoefficient,
effectivelyMotionless: 0.1,
);
_scaleAnimation =
Tween<double>(
begin: scale,
end: frictionSimulation.x(tFinal),
).animate(
CurvedAnimation(
parent: _scaleController,
curve: Curves.decelerate,
),
)
..addListener(_handleScaleAnimation);
_scaleController
..duration = Duration(milliseconds: (tFinal * 1000).round())
..forward();
case _GestureType.rotate || null:
break;
}
}
void _receivedPointerSignal(PointerSignalEvent event) {
final Offset local = event.localPosition;
final Offset global = event.position;
final double scaleChange;
if (event is PointerScrollEvent) {
if (event.kind == PointerDeviceKind.trackpad) {
widget.onInteractionStart?.call(
ScaleStartDetails(focalPoint: global, localFocalPoint: local),
);
final Offset localDelta = PointerEvent.transformDeltaViaPositions(
untransformedEndPosition: global + event.scrollDelta,
untransformedDelta: event.scrollDelta,
transform: event.transform,
);
final Offset focalPointScene = _transformer.toScene(local);
final Offset newFocalPointScene = _transformer.toScene(
local - localDelta,
);
_transformer.value = _matrixTranslate(
_transformer.value,
newFocalPointScene - focalPointScene,
);
widget.onInteractionUpdate?.call(
ScaleUpdateDetails(
focalPoint: global - event.scrollDelta,
localFocalPoint: local - localDelta,
focalPointDelta: -localDelta,
),
);
widget.onInteractionEnd?.call(ScaleEndDetails());
return;
}
_handlePointerScrollEvent(event);
return;
} else if (event is PointerScaleEvent) {
scaleChange = event.scale;
} else {
return;
}
widget.onInteractionStart?.call(
ScaleStartDetails(focalPoint: global, localFocalPoint: local),
);
if (!_gestureIsSupported(_GestureType.scale)) {
widget.onInteractionUpdate?.call(
ScaleUpdateDetails(
focalPoint: global,
localFocalPoint: local,
scale: scaleChange,
),
);
widget.onInteractionEnd?.call(ScaleEndDetails());
return;
}
final Offset focalPointScene = _transformer.toScene(local);
_transformer.value = _matrixScale(_transformer.value, scaleChange);
// After scaling, translate such that the event's position is at the
// same scene point before and after the scale.
final Offset focalPointSceneScaled = _transformer.toScene(local);
_transformer.value = _matrixTranslate(
_transformer.value,
focalPointSceneScaled - focalPointScene,
);
widget.onInteractionUpdate?.call(
ScaleUpdateDetails(
focalPoint: global,
localFocalPoint: local,
scale: scaleChange,
),
);
widget.onInteractionEnd?.call(ScaleEndDetails());
}
void _handlePointerScrollEvent(PointerScrollEvent event) {
final Offset local = event.localPosition;
final Offset global = event.position;
if (_gestureIsSupported(_GestureType.scale)) {
late final shift = HardwareKeyboard.instance.isShiftPressed;
if (HardwareKeyboard.instance.isControlPressed) {
_handleMouseWheelScale(event, local, global);
return;
} else if (shift || HardwareKeyboard.instance.isAltPressed) {
_handleMouseWheelPanAsScale(event, local, global, shift);
return;
} else {
widget.pointerSignalFallback?.call(event);
}
}
widget.onInteractionUpdate?.call(
ScaleUpdateDetails(
focalPoint: global,
localFocalPoint: local,
scale: math.exp(-event.scrollDelta.dy / widget.scaleFactor),
),
);
widget.onInteractionEnd?.call(ScaleEndDetails());
}
void _handleMouseWheelScale(
PointerScrollEvent event,
Offset local,
Offset global,
) {
final double scaleChange = math.exp(
-event.scrollDelta.dy / widget.scaleFactor,
);
final Offset focalPointScene = _transformer.toScene(local);
_transformer.value = _matrixScale(_transformer.value, scaleChange);
final Offset focalPointSceneScaled = _transformer.toScene(local);
_transformer.value = _matrixTranslate(
_transformer.value,
focalPointSceneScaled - focalPointScene,
);
widget.onInteractionUpdate?.call(
ScaleUpdateDetails(
focalPoint: global,
localFocalPoint: local,
scale: scaleChange,
),
);
widget.onInteractionEnd?.call(ScaleEndDetails());
}
void _handleMouseWheelPanAsScale(
PointerScrollEvent event,
Offset local,
Offset global,
bool flip,
) {
final Offset translation = flip
? event.scrollDelta.flip
: event.scrollDelta;
final Offset focalPointScene = _transformer.toScene(local);
final Offset newFocalPointScene = _transformer.toScene(local - translation);
_transformer.value = _matrixTranslate(
_transformer.value,
newFocalPointScene - focalPointScene,
);
}
void _handleInertiaAnimation() {
if (!_controller.isAnimating) {
_currentAxis = null;
_animation?.removeListener(_handleInertiaAnimation);
_animation = null;
_controller.reset();
return;
}
final Vector3 translationVector = _transformer.value.getTranslation();
final Offset translation = Offset(translationVector.x, translationVector.y);
_transformer.value = _matrixTranslate(
_transformer.value,
_transformer.toScene(_animation!.value) -
_transformer.toScene(translation),
);
}
void _handleScaleAnimation() {
if (!_scaleController.isAnimating) {
_currentAxis = null;
_scaleAnimation?.removeListener(_handleScaleAnimation);
_scaleAnimation = null;
_scaleController.reset();
return;
}
final double desiredScale = _scaleAnimation!.value;
final double scaleChange =
desiredScale / _transformer.value.getMaxScaleOnAxis();
final Offset referenceFocalPoint = _transformer.toScene(
_scaleAnimationFocalPoint,
);
_transformer.value = _matrixScale(_transformer.value, scaleChange);
final Offset focalPointSceneScaled = _transformer.toScene(
_scaleAnimationFocalPoint,
);
_transformer.value = _matrixTranslate(
_transformer.value,
focalPointSceneScaled - referenceFocalPoint,
);
}
void _handleTransformation() {
setState(() {});
}
void _onPointerDown(PointerDownEvent event) {
widget.onPointerDown?.call(event);
_scaleGestureRecognizer.addPointer(event);
}
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_scaleController = AnimationController(vsync: this);
_transformer.addListener(_handleTransformation);
}
@override
void didUpdateWidget(MouseInteractiveViewer oldWidget) {
super.didUpdateWidget(oldWidget);
final TransformationController? newController =
widget.transformationController;
if (newController == oldWidget.transformationController) {
return;
}
_transformer.removeListener(_handleTransformation);
if (oldWidget.transformationController == null) {
_transformer.dispose();
}
_transformer = newController ?? TransformationController();
_transformer.addListener(_handleTransformation);
}
@override
void dispose() {
_scaleGestureRecognizer.dispose();
_controller.dispose();
_scaleController.dispose();
_transformer.removeListener(_handleTransformation);
if (widget.transformationController == null) {
_transformer.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
assert(widget.child.key == widget.childKey);
return Listener(
key: _parentKey,
behavior: HitTestBehavior.opaque,
onPointerSignal: _receivedPointerSignal,
onPointerDown: _onPointerDown,
onPointerPanZoomStart: _scaleGestureRecognizer.addPointerPanZoom,
onPointerPanZoomUpdate: widget.onPointerPanZoomUpdate,
onPointerPanZoomEnd: widget.onPointerPanZoomEnd,
child: _InteractiveViewerBuilt(
childKey: widget.childKey,
clipBehavior: widget.clipBehavior,
constrained: widget.constrained,
matrix: _transformer.value,
alignment: widget.alignment,
child: widget.child,
),
);
}
}
class _InteractiveViewerBuilt extends StatelessWidget {
const _InteractiveViewerBuilt({
required this.child,
required this.childKey,
required this.clipBehavior,
required this.constrained,
required this.matrix,
required this.alignment,
});
final Widget child;
final GlobalKey childKey;
final Clip clipBehavior;
final bool constrained;
final Matrix4 matrix;
final Alignment? alignment;
@override
Widget build(BuildContext context) {
Widget child = Transform(
transform: matrix,
alignment: alignment,
child: this.child,
);
if (!constrained) {
child = OverflowBox(
alignment: Alignment.topLeft,
minWidth: 0.0,
minHeight: 0.0,
maxWidth: double.infinity,
maxHeight: double.infinity,
child: child,
);
}
if (clipBehavior != Clip.none) {
child = ClipRect(clipBehavior: clipBehavior, child: child);
}
return child;
}
}
enum _GestureType { pan, scale, rotate }
double _getFinalTime(
double velocity,
double drag, {
double effectivelyMotionless = 10,
}) {
return math.log(effectivelyMotionless / velocity) / math.log(drag / 100);
}
Offset _getMatrixTranslation(Matrix4 matrix) {
final Vector3 nextTranslation = matrix.getTranslation();
return Offset(nextTranslation.x, nextTranslation.y);
}
Quad _transformViewport(Matrix4 matrix, Rect viewport) {
final Matrix4 inverseMatrix = matrix.clone()..invert();
return Quad.points(
inverseMatrix.transform3(
Vector3(viewport.topLeft.dx, viewport.topLeft.dy, 0.0),
),
inverseMatrix.transform3(
Vector3(viewport.topRight.dx, viewport.topRight.dy, 0.0),
),
inverseMatrix.transform3(
Vector3(viewport.bottomRight.dx, viewport.bottomRight.dy, 0.0),
),
inverseMatrix.transform3(
Vector3(viewport.bottomLeft.dx, viewport.bottomLeft.dy, 0.0),
),
);
}
Quad _getAxisAlignedBoundingBoxWithRotation(Rect rect, double rotation) {
final Matrix4 rotationMatrix = Matrix4.identity()
..translateByDouble(rect.size.width / 2, rect.size.height / 2, 0, 1)
..rotateZ(rotation)
..translateByDouble(-rect.size.width / 2, -rect.size.height / 2, 0, 1);
final Quad boundariesRotated = Quad.points(
rotationMatrix.transform3(Vector3(rect.left, rect.top, 0.0)),
rotationMatrix.transform3(Vector3(rect.right, rect.top, 0.0)),
rotationMatrix.transform3(Vector3(rect.right, rect.bottom, 0.0)),
rotationMatrix.transform3(Vector3(rect.left, rect.bottom, 0.0)),
);
// ignore: invalid_use_of_visible_for_testing_member
return InteractiveViewer.getAxisAlignedBoundingBox(boundariesRotated);
}
Offset _exceedsBy(Quad boundary, Quad viewport) {
final List<Vector3> viewportPoints = <Vector3>[
viewport.point0,
viewport.point1,
viewport.point2,
viewport.point3,
];
Offset largestExcess = Offset.zero;
for (final Vector3 point in viewportPoints) {
// ignore: invalid_use_of_visible_for_testing_member
final Vector3 pointInside = InteractiveViewer.getNearestPointInside(
point,
boundary,
);
final Offset excess = Offset(
pointInside.x - point.x,
pointInside.y - point.y,
);
if (excess.dx.abs() > largestExcess.dx.abs()) {
largestExcess = Offset(excess.dx, largestExcess.dy);
}
if (excess.dy.abs() > largestExcess.dy.abs()) {
largestExcess = Offset(largestExcess.dx, excess.dy);
}
}
return _round(largestExcess);
}
Offset _round(Offset offset) {
return Offset(
double.parse(offset.dx.toStringAsFixed(9)),
double.parse(offset.dy.toStringAsFixed(9)),
);
}
Offset _alignAxis(Offset offset, Axis axis) {
return switch (axis) {
Axis.horizontal => Offset(offset.dx, 0.0),
Axis.vertical => Offset(0.0, offset.dy),
};
}
Axis? _getPanAxis(Offset point1, Offset point2) {
if (point1 == point2) {
return null;
}
final double x = point2.dx - point1.dx;
final double y = point2.dy - point1.dy;
return x.abs() > y.abs() ? Axis.horizontal : Axis.vertical;
}
extension on Offset {
Offset get flip => Offset(dy, dx);
}

View File

@@ -277,7 +277,7 @@ class MyApp extends StatelessWidget {
final plCtr = PlPlayerController.instance;
if (plCtr != null) {
if (plCtr.isFullScreen.value == true) {
if (plCtr.isFullScreen.value) {
plCtr
..triggerFullScreen(status: false)
..controlsLock.value = false;

View File

@@ -131,7 +131,6 @@ class _PlDanmakuState extends State<PlDanmaku> {
e.colorful == DmColorfulType.VipGradualColor,
count: e.hasCount() ? e.count : null,
selfSend: e.isSelf,
extra: VideoDanmaku(id: e.id.toInt(), mid: e.midHash),
),
);
}

View File

@@ -371,11 +371,6 @@ class LiveRoomController extends GetxController {
: DmUtils.decimalToColor(extra['color']),
type: DmUtils.getPosition(extra['mode']),
selfSend: extra['send_from_me'] ?? false,
extra: LiveDanmaku(
id: extra['id_str'],
mid: uid,
uname: user['base']['name'],
),
),
);
if (!disableAutoScroll.value) {

View File

@@ -211,8 +211,8 @@ class _LiveRoomPageState extends State<LiveRoomPage>
required double width,
required double height,
bool isPipMode = false,
Color? fill,
Alignment? alignment,
Color fill = Colors.black,
Alignment alignment = Alignment.center,
bool needDm = true,
}) {
if (!isFullScreen && !plPlayerController.isDesktopPip) {
@@ -472,7 +472,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
height: videoHeight,
isFullScreen,
needDm: isFullScreen,
alignment: isFullScreen ? null : Alignment.topCenter,
alignment: isFullScreen ? Alignment.center : Alignment.topCenter,
),
),
Positioned(

View File

@@ -29,13 +29,6 @@ List<SettingsModel> get playSettings => [
setKey: SettingBoxKey.enableShowDanmaku,
defaultVal: true,
),
// const SettingsModel(
// settingsType: SettingsType.sw1tch,
// title: '启用点击弹幕',
// leading: Icon(Icons.touch_app_outlined),
// setKey: SettingBoxKey.enableTapDm,
// defaultVal: false,
// ),
SettingsModel(
settingsType: SettingsType.normal,
onTap: (setState) => Get.toNamed('/playSpeedSet'),

View File

@@ -165,17 +165,18 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
late final ctr = videoDetailController.plPlayerController;
if (state == AppLifecycleState.resumed) {
if (!videoDetailController.plPlayerController.showDanmaku) {
if (!ctr.showDanmaku) {
introController.startTimer();
videoDetailController.plPlayerController.showDanmaku = true;
ctr.showDanmaku = true;
// 修复从后台恢复时全屏状态下屏幕方向错误的问题
if (isFullScreen && Platform.isIOS) {
WidgetsBinding.instance.addPostFrameCallback((_) {
// 根据视频方向重新设置屏幕方向
final isVertical = videoDetailController.isVertical.value;
final mode = plPlayerController?.mode;
final mode = ctr.mode;
if (!(mode == FullScreenMode.vertical ||
(mode == FullScreenMode.auto && isVertical) ||
@@ -188,7 +189,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
}
} else if (state == AppLifecycleState.paused) {
introController.canelTimer();
videoDetailController.plPlayerController.showDanmaku = false;
ctr.showDanmaku = false;
}
}

View File

@@ -10,6 +10,7 @@ import 'package:PiliPlus/common/widgets/dialog/report.dart';
import 'package:PiliPlus/common/widgets/marquee.dart';
import 'package:PiliPlus/http/danmaku.dart';
import 'package:PiliPlus/http/danmaku_block.dart';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/models/common/super_resolution_type.dart';
import 'package:PiliPlus/models/common/video/audio_quality.dart';
import 'package:PiliPlus/models/common/video/cdn_type.dart';
@@ -30,6 +31,7 @@ import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart';
import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart';
import 'package:PiliPlus/services/service_locator.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/accounts/account.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/image_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart';
@@ -987,16 +989,19 @@ class HeaderControlState extends State<HeaderControl> {
onTap: () async {
Get.back();
try {
final res = await Dio().get(
final res = await Request.dio.get<Uint8List>(
item.subtitleUrl!.http2https,
options: Options(responseType: ResponseType.bytes),
options: Options(
responseType: ResponseType.bytes,
extra: {'account': const NoAccount()},
),
);
if (res.statusCode == 200) {
final Uint8List bytes = res.data;
final bytes = res.data!;
final name =
'${introController.videoDetail.value.title}-${videoDetailCtr.bvid}-${videoDetailCtr.cid.value}-${item.lanDoc}.json';
final path = await FilePicker.platform.saveFile(
allowedExtensions: ['json'],
allowedExtensions: const ['json'],
type: FileType.custom,
fileName: name,
bytes: Utils.isDesktop ? null : bytes,

View File

@@ -119,10 +119,6 @@ class PlPlayerController {
late final RxBool _continuePlayInBackground =
Pref.continuePlayInBackground.obs;
late final RxBool _flipX = false.obs;
late final RxBool _flipY = false.obs;
///
final RxBool _isSliderMoving = false.obs;
PlaylistMode _looping = PlaylistMode.none;
@@ -231,9 +227,9 @@ class PlPlayerController {
late final RxBool onlyPlayAudio = false.obs;
/// 镜像
RxBool get flipX => _flipX;
late final RxBool flipX = false.obs;
RxBool get flipY => _flipY;
late final RxBool flipY = false.obs;
/// 是否长按倍速
RxBool get longPressStatus => _longPressStatus;
@@ -324,7 +320,6 @@ class PlPlayerController {
}
/// 弹幕权重
late final enableTapDm = Pref.enableTapDm;
late int danmakuWeight = Pref.danmakuWeight;
late RuleFilter filters = Pref.danmakuFilterRule;
// 关联弹幕控制器

View File

@@ -4,6 +4,7 @@ import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/gesture/interactive_viewer.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/pair.dart';
import 'package:PiliPlus/common/widgets/progress_bar/audio_video_progress_bar.dart';
@@ -21,12 +22,10 @@ import 'package:PiliPlus/models_new/video/video_detail/section.dart';
import 'package:PiliPlus/models_new/video/video_detail/ugc_season.dart';
import 'package:PiliPlus/models_new/video/video_shot/data.dart';
import 'package:PiliPlus/pages/common/common_intro_controller.dart';
import 'package:PiliPlus/pages/danmaku/dnamaku_model.dart';
import 'package:PiliPlus/pages/video/controller.dart';
import 'package:PiliPlus/pages/video/introduction/pgc/controller.dart';
import 'package:PiliPlus/pages/video/post_panel/popup_menu_text.dart';
import 'package:PiliPlus/pages/video/post_panel/view.dart';
import 'package:PiliPlus/pages/video/widgets/header_control.dart';
import 'package:PiliPlus/plugin/pl_player/controller.dart';
import 'package:PiliPlus/plugin/pl_player/models/bottom_control_type.dart';
import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart';
@@ -48,7 +47,6 @@ import 'package:PiliPlus/utils/image_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:canvas_danmaku/canvas_danmaku.dart';
import 'package:dio/dio.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:fl_chart/fl_chart.dart';
@@ -64,6 +62,7 @@ import 'package:get/get.dart' hide ContextExtensionss;
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:screen_brightness_platform_interface/screen_brightness_platform_interface.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:window_manager/window_manager.dart';
class PLVideoPlayer extends StatefulWidget {
@@ -76,12 +75,10 @@ class PLVideoPlayer extends StatefulWidget {
required this.headerControl,
this.bottomControl,
this.danmuWidget,
this.customWidget,
this.customWidgets,
this.showEpisodes,
this.showViewPoints,
this.fill,
this.alignment,
this.fill = Colors.black,
this.alignment = Alignment.center,
super.key,
});
@@ -93,31 +90,26 @@ class PLVideoPlayer extends StatefulWidget {
final Widget headerControl;
final Widget? bottomControl;
final Widget? danmuWidget;
// List<Widget> or Widget
final Widget? customWidget;
final List<Widget>? customWidgets;
final void Function([int?, UgcSeason?, dynamic, String?, int?, int?])?
showEpisodes;
final VoidCallback? showViewPoints;
final Color? fill;
final Alignment? alignment;
final Color fill;
final Alignment alignment;
@override
State<PLVideoPlayer> createState() => _PLVideoPlayerState();
}
class _PLVideoPlayerState extends State<PLVideoPlayer>
with TickerProviderStateMixin {
with WidgetsBindingObserver, TickerProviderStateMixin {
late AnimationController animationController;
late VideoController videoController;
late final CommonIntroController introController = widget.introController!;
late final VideoDetailController videoDetailController =
widget.videoDetailController!;
final GlobalKey _playerKey = GlobalKey();
final GlobalKey<VideoState> key = GlobalKey<VideoState>();
final _playerKey = GlobalKey();
final _videoKey = GlobalKey();
final RxDouble _brightnessValue = 0.0.obs;
final RxBool _brightnessIndicator = false.obs;
@@ -139,19 +131,28 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
StreamSubscription? _listener;
StreamSubscription? _controlsListener;
@override
void didUpdateWidget(PLVideoPlayer oldWidget) {
super.didUpdateWidget(oldWidget);
if (plPlayerController.enableTapDm &&
(widget.maxWidth != oldWidget.maxWidth ||
widget.maxHeight != maxHeight)) {
_removeOverlay();
}
}
bool _pauseDueToPauseUponEnteringBackgroundMode = false;
StreamSubscription<bool>? wakeLock;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
late final player = plPlayerController.videoController?.player;
if (player != null && player.state.playing) {
WakelockPlus.enable();
}
wakeLock = player?.stream.playing.listen(
(value) {
if (value) {
WakelockPlus.enable();
} else {
WakelockPlus.disable();
}
},
);
_controlsListener = plPlayerController.showControls.listen((bool val) {
final visible = val && !plPlayerController.controlsLock.value;
if (widget.videoDetailController?.headerCtrKey.currentState?.provider
@@ -218,6 +219,27 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
..onDoubleTapDown = onDoubleTapDown;
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (!plPlayerController.continuePlayInBackground.value) {
late final player = plPlayerController.videoController?.player;
if (const [
AppLifecycleState.paused,
AppLifecycleState.detached,
].contains(state)) {
if (player != null && player.state.playing) {
_pauseDueToPauseUponEnteringBackgroundMode = true;
player.pause();
}
} else {
if (_pauseDueToPauseUponEnteringBackgroundMode) {
_pauseDueToPauseUponEnteringBackgroundMode = false;
player?.play();
}
}
}
}
Future<void> setBrightness(double value) async {
try {
await ScreenBrightnessPlatform.instance.setApplicationScreenBrightness(
@@ -236,6 +258,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
wakeLock?.cancel();
WakelockPlus.enabled.then((i) {
if (i) WakelockPlus.disable();
});
_tapGestureRecognizer.dispose();
_longPressRecognizer?.dispose();
_doubleTapGestureRecognizer.dispose();
@@ -831,7 +858,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
if (details.localFocalPoint.dx < 40) return;
if (details.localFocalPoint.dx > maxWidth - 40) return;
if (details.localFocalPoint.dy > maxHeight - 40) return;
if (details.pointerCount == 2) {
if (details.pointerCount > 1) {
interacting = true;
}
plPlayerController.initialFocalPoint = details.localFocalPoint;
@@ -848,7 +875,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
}
Offset cumulativeDelta =
details.localFocalPoint - plPlayerController.initialFocalPoint;
if (details.pointerCount == 2 && cumulativeDelta.distance < 1.5) {
if (details.pointerCount > 1 && cumulativeDelta.distance < 1.5) {
interacting = true;
_gestureType = null;
return;
@@ -1072,25 +1099,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
void onTapUp(TapUpDetails details) {
switch (details.kind) {
case ui.PointerDeviceKind.mouse when (Utils.isDesktop):
case ui.PointerDeviceKind.mouse when Utils.isDesktop:
onTapDesktop();
break;
default:
if (kDebugMode && isMobile) {
final ctr = plPlayerController.danmakuController;
if (ctr != null) {
final item = ctr.findSingleDanmaku(details.localPosition);
if (item == null) {
if (_suspendedDm.value != null) {
_removeOverlay();
break;
}
} else if (item != _suspendedDm.value?.item) {
_showOverlay(item, details, ctr);
break;
}
}
}
plPlayerController.controls = !plPlayerController.showControls.value;
break;
}
@@ -1098,7 +1110,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
void onDoubleTapDown(TapDownDetails details) {
switch (details.kind) {
case ui.PointerDeviceKind.mouse when !isMobile:
case ui.PointerDeviceKind.mouse when Utils.isDesktop:
onDoubleTapDesktop();
break;
default:
@@ -1113,12 +1125,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
_longPressRecognizer ??= LongPressGestureRecognizer()
..onLongPressStart = ((_) =>
plPlayerController.setLongPressStatus(true))
..onLongPressEnd = ((_) =>
plPlayerController.setLongPressStatus(false));
..onLongPressEnd = (_) => plPlayerController.setLongPressStatus(false);
late final TapGestureRecognizer _tapGestureRecognizer;
late final DoubleTapGestureRecognizer _doubleTapGestureRecognizer;
void onPointerDown(PointerDownEvent event) {
void _onPointerDown(PointerDownEvent event) {
if (!isMobile) {
final buttons = event.buttons;
final isSecondaryBtn = buttons == kSecondaryMouseButton;
@@ -1135,11 +1146,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
}
}
_tapGestureRecognizer.addPointer(event);
_doubleTapGestureRecognizer.addPointer(event);
if (!plPlayerController.isLive) {
longPressRecognizer.addPointer(event);
}
_tapGestureRecognizer.addPointer(event);
_doubleTapGestureRecognizer.addPointer(event);
}
void _showControlsIfNeeded() {
@@ -1155,7 +1166,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
}
}
void onPointerPanZoomUpdate(PointerPanZoomUpdateEvent event) {
void _onPointerPanZoomUpdate(PointerPanZoomUpdateEvent event) {
if (plPlayerController.controlsLock.value) return;
if (_gestureType == null) {
final pan = event.pan;
@@ -1213,11 +1224,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
}
}
void onPointerPanZoomEnd(PointerPanZoomEndEvent event) {
void _onPointerPanZoomEnd(PointerPanZoomEndEvent event) {
_gestureType = null;
}
void onPointerSignal(PointerSignalEvent event) {
void _onPointerSignal(PointerSignalEvent event) {
if (event is PointerScrollEvent) {
final offset = -event.scrollDelta.dy / 4000;
final volume = clampDouble(
@@ -1245,58 +1256,29 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
final isFullScreen = this.isFullScreen;
final isLive = plPlayerController.isLive;
final gestureWidget = Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: onPointerDown,
onPointerPanZoomUpdate: isMobile ? null : onPointerPanZoomUpdate,
onPointerPanZoomEnd: isMobile ? null : onPointerPanZoomEnd,
onPointerSignal: isMobile ? null : onPointerSignal,
);
final child = Stack(
fit: StackFit.passthrough,
key: _playerKey,
children: <Widget>[
Obx(
() {
final videoFit = plPlayerController.videoFit.value;
return Video(
key: key,
width: maxWidth,
height: maxHeight,
fill: widget.fill ?? Colors.black,
alignment: widget.alignment ?? Alignment.center,
controller: videoController,
controls: NoVideoControls,
pauseUponEnteringBackgroundMode:
!plPlayerController.continuePlayInBackground.value,
resumeUponEnteringForegroundMode: true,
// 字幕尺寸调节
subtitleViewConfiguration: isLive
? const SubtitleViewConfiguration()
: plPlayerController.subtitleConfig.value,
fit: videoFit.boxFit,
aspectRatio: videoFit.aspectRatio,
dmWidget: widget.danmuWidget,
transformationController: transformationController,
scaleEnabled: isMobile && !plPlayerController.controlsLock.value,
enableShrinkVideoSize:
isMobile && plPlayerController.enableShrinkVideoSize,
onInteractionStart: _onInteractionStart, // TODO: refa gesture
onInteractionUpdate: _onInteractionUpdate,
onInteractionEnd: _onInteractionEnd,
flipX: plPlayerController.flipX.value,
flipY: plPlayerController.flipY.value,
gestureWidget: gestureWidget,
enableDragSubtitle: plPlayerController.enableDragSubtitle,
onUpdatePadding: plPlayerController.onUpdatePadding,
);
},
),
_videoWidget,
// /// 弹幕面板
// if (widget.danmuWidget != null)
// Positioned.fill(top: 4, child: widget.danmuWidget!),
if (widget.danmuWidget case final danmaku?)
Positioned.fill(child: danmaku),
if (!isLive)
Positioned.fill(
child: IgnorePointer(
ignoring: !plPlayerController.enableDragSubtitle,
child: Obx(
() => SubtitleView(
controller: videoController,
configuration: plPlayerController.subtitleConfig.value,
enableDragSubtitle: plPlayerController.enableDragSubtitle,
onUpdatePadding: plPlayerController.onUpdatePadding,
),
),
),
),
/// 长按倍速 toast
if (!isLive)
@@ -1928,102 +1910,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
)
: const SizedBox.shrink();
}),
Obx(() {
if (_suspendedDm.value case final suspendedDm?) {
final offset = suspendedDm.offset;
final item = suspendedDm.item;
final extra = item.content.extra as VideoDanmaku;
return Positioned(
left: offset.dx,
top: offset.dy,
child: Column(
children: [
const CustomPaint(
painter: _TrianglePainter(Colors.black54),
size: Size(12, 6),
),
Container(
width: overlayWidth,
height: overlayHeight,
decoration: const BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.all(Radius.circular(18)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_overlayItem(
Icon(
size: 20,
extra.isLike
? Icons.thumb_up_off_alt_sharp
: Icons.thumb_up_off_alt_outlined,
color: Colors.white,
),
onTap: () {
_removeOverlay();
HeaderControl.likeDanmaku(
extra,
plPlayerController.cid!,
);
},
),
_overlayItem(
const Icon(
size: 20,
Icons.copy,
color: Colors.white,
),
onTap: () {
_removeOverlay();
Utils.copyText(item.content.text);
},
),
if (item.content.selfSend)
_overlayItem(
const Icon(
size: 20,
Icons.delete,
color: Colors.white,
),
onTap: () {
_removeOverlay();
HeaderControl.deleteDanmaku(
extra.id,
plPlayerController.cid!,
);
},
)
else
_overlayItem(
const Icon(
size: 20,
Icons.report_problem_outlined,
color: Colors.white,
),
onTap: () {
_removeOverlay();
HeaderControl.reportDanmaku(
extra,
context,
plPlayerController,
);
},
),
],
),
),
],
),
);
}
return const SizedBox.shrink();
}),
],
);
if (!isMobile) {
if (Utils.isDesktop) {
return Obx(
() => MouseRegion(
cursor: !plPlayerController.showControls.value && isFullScreen
@@ -2040,6 +1929,58 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
return child;
}
Widget get _videoWidget {
return Container(
clipBehavior: Clip.none,
width: maxWidth,
height: maxHeight,
color: widget.fill,
child: Obx(
() => MouseInteractiveViewer(
scaleEnabled: !plPlayerController.controlsLock.value,
pointerSignalFallback: _onPointerSignal,
onPointerPanZoomUpdate: _onPointerPanZoomUpdate,
onPointerPanZoomEnd: _onPointerPanZoomEnd,
onPointerDown: _onPointerDown,
onInteractionStart: _onInteractionStart,
onInteractionUpdate: _onInteractionUpdate,
onInteractionEnd: _onInteractionEnd,
panEnabled: false,
minScale: plPlayerController.enableShrinkVideoSize ? 0.75 : 1,
maxScale: 2.0,
boundaryMargin: plPlayerController.enableShrinkVideoSize
? const EdgeInsets.all(double.infinity)
: EdgeInsets.zero,
panAxis: PanAxis.aligned,
transformationController: transformationController,
childKey: _videoKey,
child: RepaintBoundary(
key: _videoKey,
child: Obx(
() {
final videoFit = plPlayerController.videoFit.value;
return Transform.flip(
flipX: plPlayerController.flipX.value,
flipY: plPlayerController.flipY.value,
filterQuality: FilterQuality.low,
child: FittedBox(
fit: videoFit.boxFit,
alignment: widget.alignment,
child: SimpleVideo(
controller: plPlayerController.videoController!,
fill: widget.fill,
aspectRatio: videoFit.aspectRatio,
),
),
);
},
),
),
),
),
);
}
late final segment = Pair(
first: plPlayerController.position.value.inMilliseconds / 1000.0,
second: plPlayerController.position.value.inMilliseconds / 1000.0,
@@ -2183,54 +2124,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
},
);
}
static const overlaySpacing = 10.0;
static const overlayWidth = 130.0;
static const overlayHeight = 35.0;
final Rx<({Offset offset, DanmakuItem item})?> _suspendedDm =
Rx<({Offset offset, DanmakuItem item})?>(null);
void _removeOverlay() {
_suspendedDm
..value?.item.suspend = false
..value = null;
}
Widget _overlayItem(Widget child, {required VoidCallback onTap}) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: SizedBox(
height: overlayHeight,
width: overlayWidth / 3,
child: Center(
child: child,
),
),
);
}
void _showOverlay(
DanmakuItem<DanmakuExtra> item,
TapUpDetails event,
DanmakuController<DanmakuExtra> ctr,
) {
_removeOverlay();
item.suspend = true;
final dy = item.content.type == DanmakuItemType.bottom
? ctr.viewHeight - item.yPosition - item.height
: item.yPosition;
final top = dy + item.height;
final left = clampDouble(
event.localPosition.dx - overlayWidth / 2,
overlaySpacing,
ctr.viewWidth - overlayWidth - overlaySpacing,
);
_suspendedDm.value = (offset: Offset(left, top), item: item);
}
}
Widget buildDmChart(
@@ -2575,27 +2468,3 @@ Widget buildViewPointWidget(
),
);
}
class _TrianglePainter extends CustomPainter {
const _TrianglePainter(this.color);
final Color color;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.fill;
final path = Path()
..moveTo(0, size.height)
..lineTo(size.width, size.height)
..lineTo(size.width / 2, 0)
..close();
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant _TrianglePainter oldDelegate) =>
color != oldDelegate.color;
}

View File

@@ -226,7 +226,7 @@ class LiveMessageStream {
}
_processingData(decompressedData);
} catch (e) {
if (kDebugMode) logger.i(e);
if (kDebugMode) rethrow;
}
}
},
@@ -256,7 +256,7 @@ class LiveMessageStream {
}
}
} catch (e) {
if (kDebugMode) logger.i('ParseHeader错误: $e');
if (kDebugMode) rethrow;
}
}

View File

@@ -141,8 +141,7 @@ abstract class SettingBoxKey {
showFsLockBtn = 'showFsLockBtn',
silentDownImg = 'silentDownImg',
showMemberShop = 'showMemberShop',
enablePlayAll = 'enablePlayAll',
enableTapDm = 'enableTapDm';
enablePlayAll = 'enablePlayAll';
static const String minimizeOnExit = 'minimizeOnExit',
windowSize = 'windowSize',

View File

@@ -858,7 +858,4 @@ abstract class Pref {
static bool get enablePlayAll =>
_setting.get(SettingBoxKey.enablePlayAll, defaultValue: true);
static bool get enableTapDm =>
_setting.get(SettingBoxKey.enableTapDm, defaultValue: false);
}

View File

@@ -223,7 +223,7 @@ packages:
description:
path: "."
ref: main
resolved-ref: "454790117e05e96782b05ee3d28d0283741b3cde"
resolved-ref: "5bbf0b0903447862fb04927f13530db701e2cfcf"
url: "https://github.com/bggRGjQaUbCoE/canvas_danmaku.git"
source: git
version: "0.2.6"
@@ -1097,7 +1097,7 @@ packages:
description:
path: media_kit
ref: "version_1.2.5"
resolved-ref: "8f15c273f3e5c05476d8b19881719a1bd906c4f2"
resolved-ref: ebc4b1a4a3ec3170898e2a502e17c0a25289eb53
url: "https://github.com/bggRGjQaUbCoE/media-kit.git"
source: git
version: "1.1.11"
@@ -1106,7 +1106,7 @@ packages:
description:
path: "libs/android/media_kit_libs_android_video"
ref: "version_1.2.5"
resolved-ref: "8f15c273f3e5c05476d8b19881719a1bd906c4f2"
resolved-ref: ebc4b1a4a3ec3170898e2a502e17c0a25289eb53
url: "https://github.com/bggRGjQaUbCoE/media-kit.git"
source: git
version: "1.3.7"
@@ -1139,7 +1139,7 @@ packages:
description:
path: "libs/universal/media_kit_libs_video"
ref: "version_1.2.5"
resolved-ref: "8f15c273f3e5c05476d8b19881719a1bd906c4f2"
resolved-ref: ebc4b1a4a3ec3170898e2a502e17c0a25289eb53
url: "https://github.com/bggRGjQaUbCoE/media-kit.git"
source: git
version: "1.0.5"
@@ -1148,7 +1148,7 @@ packages:
description:
path: "libs/windows/media_kit_libs_windows_video"
ref: "version_1.2.5"
resolved-ref: "8f15c273f3e5c05476d8b19881719a1bd906c4f2"
resolved-ref: ebc4b1a4a3ec3170898e2a502e17c0a25289eb53
url: "https://github.com/bggRGjQaUbCoE/media-kit.git"
source: git
version: "1.0.10"
@@ -1157,7 +1157,7 @@ packages:
description:
path: media_kit_native_event_loop
ref: "version_1.2.5"
resolved-ref: "8f15c273f3e5c05476d8b19881719a1bd906c4f2"
resolved-ref: ebc4b1a4a3ec3170898e2a502e17c0a25289eb53
url: "https://github.com/bggRGjQaUbCoE/media-kit.git"
source: git
version: "1.0.9"
@@ -1166,7 +1166,7 @@ packages:
description:
path: media_kit_video
ref: "version_1.2.5"
resolved-ref: "8f15c273f3e5c05476d8b19881719a1bd906c4f2"
resolved-ref: ebc4b1a4a3ec3170898e2a502e17c0a25289eb53
url: "https://github.com/bggRGjQaUbCoE/media-kit.git"
source: git
version: "1.2.5"