mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: danmaku seekTo (#1603)
This commit is contained in:
committed by
GitHub
parent
15fe7787ba
commit
8650c96b7b
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" data-pointer="none" viewBox="0 0 162 48"><path fill="#000" fill-opacity=".703" fill-rule="evenodd" d="M1 27.075C1 16.07 9.92 7.149 20.925 7.149h55.91L81.522 1l4.741 6.15h54.812C152.079 7.15 161 16.07 161 27.074 161 38.079 152.079 47 141.075 47H20.925C9.921 47 1 38.08 1 27.075Z" clip-rule="evenodd"></path><path stroke="#fff" stroke-linejoin="round" stroke-opacity=".496" d="M81.918.695a.5.5 0 0 0-.794.002l-4.536 5.952H20.925C9.645 6.65.5 15.794.5 27.075.5 38.355 9.645 47.5 20.925 47.5h120.15c11.28 0 20.425-9.145 20.425-20.425 0-11.281-9.145-20.426-20.425-20.426H86.509L81.918.695Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 651 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" data-pointer="none" viewBox="0 0 145 42"><path fill="#000" fill-opacity=".703" fill-rule="evenodd" d="M1 23.562c0-9.63 7.807-17.438 17.438-17.438h4.372L26.65 1l3.887 5.124h96.025c9.631 0 17.438 7.808 17.438 17.438C144 33.192 136.193 41 126.562 41H18.438C8.808 41 1 33.193 1 23.562Z" clip-rule="evenodd"></path><path stroke="#fff" stroke-linejoin="round" stroke-opacity=".496" d="M27.05.698a.5.5 0 0 0-.8.002l-3.69 4.924h-4.122C8.53 5.624.5 13.655.5 23.562.5 33.47 8.531 41.5 18.438 41.5h108.124c9.907 0 17.938-8.031 17.938-17.938 0-9.907-8.031-17.938-17.938-17.938H30.785L27.05.698Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 649 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" data-pointer="none" version="1.1" viewBox="0 0 145 42" xmlns="http://www.w3.org/2000/svg"><path d="m144 23.562c0-9.63-7.807-17.438-17.438-17.438h-4.372l-3.84-5.124-3.887 5.124h-96.025c-9.631 0-17.438 7.808-17.438 17.438s7.807 17.438 17.438 17.438h108.12c9.63 0 17.438-7.807 17.438-17.438z" clip-rule="evenodd" fill="#000" fill-opacity=".703" fill-rule="evenodd"/><path d="m117.95 0.698a0.5 0.5 0 0 1 0.8 2e-3l3.69 4.924h4.122c9.908 0 17.938 8.031 17.938 17.938 0 9.908-8.031 17.938-17.938 17.938h-108.12c-9.907 0-17.938-8.031-17.938-17.938s8.031-17.938 17.938-17.938h95.777z" stroke="#fff" stroke-linejoin="round" stroke-opacity=".496"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 660 B |
@@ -320,7 +320,7 @@ class PlPlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 弹幕权重
|
/// 弹幕权重
|
||||||
late final enableTapDm = Utils.isMobile && Pref.enableTapDm;
|
late final enableTapDm = Pref.enableTapDm;
|
||||||
late int danmakuWeight = Pref.danmakuWeight;
|
late int danmakuWeight = Pref.danmakuWeight;
|
||||||
late RuleFilter filters = Pref.danmakuFilterRule;
|
late RuleFilter filters = Pref.danmakuFilterRule;
|
||||||
// 关联弹幕控制器
|
// 关联弹幕控制器
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
|
||||||
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
|
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:get/get.dart' hide ContextExtensionss;
|
import 'package:get/get.dart' hide ContextExtensionss;
|
||||||
@@ -1139,24 +1138,22 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onTapDown(TapDownDetails details) {
|
void _onTapDown(TapDownDetails details) {
|
||||||
if (Utils.isMobile) {
|
final ctr = plPlayerController.danmakuController;
|
||||||
final ctr = plPlayerController.danmakuController;
|
if (ctr != null) {
|
||||||
if (ctr != null) {
|
final pos = details.localPosition;
|
||||||
final pos = details.localPosition;
|
final item = ctr.findSingleDanmaku(pos);
|
||||||
final item = ctr.findSingleDanmaku(pos);
|
if (item == null) {
|
||||||
if (item == null) {
|
if (_suspendedDm != null) {
|
||||||
if (_suspendedDm != null) {
|
_removeDmAction();
|
||||||
_removeDmAction();
|
|
||||||
}
|
|
||||||
} else if (item != _suspendedDm) {
|
|
||||||
if (item.content.extra == null) {
|
|
||||||
_removeDmAction();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_suspendedDm?.suspend = false;
|
|
||||||
_suspendedDm = item..suspend = true;
|
|
||||||
_dmOffset = pos;
|
|
||||||
}
|
}
|
||||||
|
} else if (item != _suspendedDm) {
|
||||||
|
if (item.content.extra == null) {
|
||||||
|
_removeDmAction();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_suspendedDm?.suspend = false;
|
||||||
|
_suspendedDm = item..suspend = true;
|
||||||
|
_dmOffset = pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2196,7 +2193,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const _overlaySpacing = 10.0;
|
static const _overlaySpacing = 10.0;
|
||||||
static const _overlayWidth = 118.0;
|
static const _overlayItemWidth = 40.0;
|
||||||
static const _overlayHeight = 35.0;
|
static const _overlayHeight = 35.0;
|
||||||
|
|
||||||
DanmakuItem<DanmakuExtra>? _suspendedDm;
|
DanmakuItem<DanmakuExtra>? _suspendedDm;
|
||||||
@@ -2219,7 +2216,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
},
|
},
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: _overlayHeight,
|
height: _overlayHeight,
|
||||||
width: _overlayWidth / 3,
|
width: _overlayItemWidth,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
@@ -2227,15 +2224,17 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getDmTipBg(DanmakuItem item) {
|
static final _timeRegExp = RegExp(r'(?:\d+[::])?\d+[::][0-5]?\d(?!\d)');
|
||||||
const offset = 65;
|
|
||||||
if (item.xPosition >= maxWidth - offset) {
|
int? _getValidOffset(String data) {
|
||||||
return 'right';
|
if (_timeRegExp.firstMatch(data) case final timeStr?) {
|
||||||
|
final offset = DurationUtils.parseDuration(timeStr.group(0));
|
||||||
|
if (0 < offset &&
|
||||||
|
offset * 1000 < videoDetailController.data.timeLength!) {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (item.xPosition + item.width <= offset) {
|
return null;
|
||||||
return 'left';
|
|
||||||
}
|
|
||||||
return 'center';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDmAction(
|
Widget _buildDmAction(
|
||||||
@@ -2249,17 +2248,24 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
return const Positioned(left: 0, top: 0, child: SizedBox.shrink());
|
return const Positioned(left: 0, top: 0, child: SizedBox.shrink());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final seekOffset = _getValidOffset(item.content.text);
|
||||||
|
|
||||||
|
final overlayWidth = _overlayItemWidth * (seekOffset == null ? 3 : 4);
|
||||||
|
|
||||||
final dy = item.content.type == DanmakuItemType.bottom
|
final dy = item.content.type == DanmakuItemType.bottom
|
||||||
? maxHeight - item.yPosition - item.height
|
? maxHeight - item.yPosition - item.height
|
||||||
: item.yPosition;
|
: item.yPosition;
|
||||||
final top = dy + item.height + 4;
|
final top = dy + item.height + 4;
|
||||||
final right =
|
|
||||||
maxWidth -
|
final realLeft = dx + overlayWidth / 2;
|
||||||
clampDouble(
|
|
||||||
dx + _overlayWidth / 2,
|
final left = realLeft.clamp(
|
||||||
_overlaySpacing + _overlayWidth,
|
_overlaySpacing + overlayWidth,
|
||||||
maxWidth - _overlaySpacing,
|
maxWidth - _overlaySpacing,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final right = maxWidth - left;
|
||||||
|
final triangleOffset = realLeft - left;
|
||||||
|
|
||||||
if (right > (maxWidth - item.xPosition)) {
|
if (right > (maxWidth - item.xPosition)) {
|
||||||
_removeDmAction();
|
_removeDmAction();
|
||||||
@@ -2271,116 +2277,112 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
return Positioned(
|
return Positioned(
|
||||||
right: right,
|
right: right,
|
||||||
top: top,
|
top: top,
|
||||||
child: SizedBox(
|
child: CustomPaint(
|
||||||
width: _overlayWidth,
|
painter: _DanmakuTipPainter(offset: triangleOffset),
|
||||||
height: _overlayHeight,
|
child: Row(
|
||||||
child: Stack(
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
SvgPicture.asset(
|
children: switch (extra) {
|
||||||
'assets/images/dm_tip/player_dm_tip_${_getDmTipBg(item)}.svg',
|
null => throw UnimplementedError(),
|
||||||
clipBehavior: Clip.none,
|
VideoDanmaku() => [
|
||||||
width: _overlayWidth,
|
_dmActionItem(
|
||||||
height: _overlayHeight,
|
extra.isLike
|
||||||
),
|
? const Icon(
|
||||||
Positioned.fill(
|
size: 20,
|
||||||
top: 4,
|
CustomIcons.player_dm_tip_like_solid,
|
||||||
child: Row(
|
color: Colors.white,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
)
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
: const Icon(
|
||||||
children: extra is VideoDanmaku
|
size: 20,
|
||||||
? [
|
CustomIcons.player_dm_tip_like,
|
||||||
_dmActionItem(
|
color: Colors.white,
|
||||||
extra.isLike
|
),
|
||||||
? const Icon(
|
onTap: () => HeaderControl.likeDanmaku(
|
||||||
size: 20,
|
extra,
|
||||||
CustomIcons.player_dm_tip_like_solid,
|
plPlayerController.cid!,
|
||||||
color: Colors.white,
|
),
|
||||||
)
|
|
||||||
: const Icon(
|
|
||||||
size: 20,
|
|
||||||
CustomIcons.player_dm_tip_like,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
onTap: () => HeaderControl.likeDanmaku(
|
|
||||||
extra,
|
|
||||||
plPlayerController.cid!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_dmActionItem(
|
|
||||||
const Icon(
|
|
||||||
size: 19,
|
|
||||||
CustomIcons.player_dm_tip_copy,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
onTap: () => Utils.copyText(item.content.text),
|
|
||||||
),
|
|
||||||
if (item.content.selfSend)
|
|
||||||
_dmActionItem(
|
|
||||||
const Icon(
|
|
||||||
size: 20,
|
|
||||||
CustomIcons.player_dm_tip_recall,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
onTap: () => HeaderControl.deleteDanmaku(
|
|
||||||
extra.id,
|
|
||||||
plPlayerController.cid!,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
_dmActionItem(
|
|
||||||
const Icon(
|
|
||||||
size: 20,
|
|
||||||
CustomIcons.player_dm_tip_back,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
onTap: () => HeaderControl.reportDanmaku(
|
|
||||||
context,
|
|
||||||
extra: extra,
|
|
||||||
ctr: plPlayerController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: extra is LiveDanmaku
|
|
||||||
? [
|
|
||||||
_dmActionItem(
|
|
||||||
const Icon(
|
|
||||||
size: 20,
|
|
||||||
MdiIcons.accountOutline,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
onTap: () => Get.toNamed('/member?mid=${extra.mid}'),
|
|
||||||
),
|
|
||||||
_dmActionItem(
|
|
||||||
const Icon(
|
|
||||||
size: 19,
|
|
||||||
CustomIcons.player_dm_tip_copy,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
onTap: () => Utils.copyText(item.content.text),
|
|
||||||
),
|
|
||||||
_dmActionItem(
|
|
||||||
const Icon(
|
|
||||||
size: 20,
|
|
||||||
CustomIcons.player_dm_tip_back,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
onTap: () => HeaderControl.reportLiveDanmaku(
|
|
||||||
context,
|
|
||||||
roomId:
|
|
||||||
(widget.bottomControl
|
|
||||||
as live_bottom.BottomControl)
|
|
||||||
.liveRoomCtr
|
|
||||||
.roomId,
|
|
||||||
msg: item.content.text,
|
|
||||||
extra: extra,
|
|
||||||
ctr: plPlayerController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: throw UnimplementedError(),
|
|
||||||
),
|
),
|
||||||
),
|
_dmActionItem(
|
||||||
],
|
const Icon(
|
||||||
|
size: 19,
|
||||||
|
CustomIcons.player_dm_tip_copy,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
onTap: () => Utils.copyText(item.content.text),
|
||||||
|
),
|
||||||
|
if (item.content.selfSend)
|
||||||
|
_dmActionItem(
|
||||||
|
const Icon(
|
||||||
|
size: 20,
|
||||||
|
CustomIcons.player_dm_tip_recall,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
onTap: () => HeaderControl.deleteDanmaku(
|
||||||
|
extra.id,
|
||||||
|
plPlayerController.cid!,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
_dmActionItem(
|
||||||
|
const Icon(
|
||||||
|
size: 20,
|
||||||
|
CustomIcons.player_dm_tip_back,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
onTap: () => HeaderControl.reportDanmaku(
|
||||||
|
context,
|
||||||
|
extra: extra,
|
||||||
|
ctr: plPlayerController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (seekOffset != null)
|
||||||
|
_dmActionItem(
|
||||||
|
const Icon(
|
||||||
|
size: 20,
|
||||||
|
Icons.gps_fixed_outlined,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
onTap: () => plPlayerController.seekTo(
|
||||||
|
Duration(seconds: seekOffset),
|
||||||
|
isSeek: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
LiveDanmaku() => [
|
||||||
|
_dmActionItem(
|
||||||
|
const Icon(
|
||||||
|
size: 20,
|
||||||
|
MdiIcons.accountOutline,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
onTap: () => Get.toNamed('/member?mid=${extra.mid}'),
|
||||||
|
),
|
||||||
|
_dmActionItem(
|
||||||
|
const Icon(
|
||||||
|
size: 19,
|
||||||
|
CustomIcons.player_dm_tip_copy,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
onTap: () => Utils.copyText(item.content.text),
|
||||||
|
),
|
||||||
|
_dmActionItem(
|
||||||
|
const Icon(
|
||||||
|
size: 20,
|
||||||
|
CustomIcons.player_dm_tip_back,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
onTap: () => HeaderControl.reportLiveDanmaku(
|
||||||
|
context,
|
||||||
|
roomId: (widget.bottomControl as live_bottom.BottomControl)
|
||||||
|
.liveRoomCtr
|
||||||
|
.roomId,
|
||||||
|
msg: item.content.text,
|
||||||
|
extra: extra,
|
||||||
|
ctr: plPlayerController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -2729,3 +2731,59 @@ Widget buildViewPointWidget(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _DanmakuTipPainter extends CustomPainter {
|
||||||
|
final double offset;
|
||||||
|
|
||||||
|
const _DanmakuTipPainter({this.offset = 0});
|
||||||
|
|
||||||
|
@override
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final paint = Paint()
|
||||||
|
..color = const Color(0xB3000000)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
final strokePaint = Paint()
|
||||||
|
..color = const Color(0x7EFFFFFF)
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 1.5;
|
||||||
|
|
||||||
|
final radius = size.height / 2;
|
||||||
|
final triangleHeight = size.height / 6;
|
||||||
|
final 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();
|
||||||
|
|
||||||
|
canvas
|
||||||
|
..drawPath(path, paint)
|
||||||
|
..drawPath(path, strokePaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant _DanmakuTipPainter oldDelegate) =>
|
||||||
|
oldDelegate.offset != offset;
|
||||||
|
}
|
||||||
|
|||||||
@@ -860,7 +860,7 @@ abstract class Pref {
|
|||||||
_setting.get(SettingBoxKey.enablePlayAll, defaultValue: true);
|
_setting.get(SettingBoxKey.enablePlayAll, defaultValue: true);
|
||||||
|
|
||||||
static bool get enableTapDm =>
|
static bool get enableTapDm =>
|
||||||
_setting.get(SettingBoxKey.enableTapDm, defaultValue: true);
|
_setting.get(SettingBoxKey.enableTapDm, defaultValue: Utils.isMobile);
|
||||||
|
|
||||||
static bool get showTrayIcon =>
|
static bool get showTrayIcon =>
|
||||||
_setting.get(SettingBoxKey.showTrayIcon, defaultValue: true);
|
_setting.get(SettingBoxKey.showTrayIcon, defaultValue: true);
|
||||||
|
|||||||
Reference in New Issue
Block a user