opt: video seek preview

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-01-30 09:51:13 +08:00
parent a5a13b45cf
commit 02af3a18ff
5 changed files with 88 additions and 62 deletions

View File

@@ -410,7 +410,7 @@ class _NineGridViewState extends State<NineGridView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget? child = Container(); Widget? child;
double? realWidth = widget.width; double? realWidth = widget.width;
double? realHeight = widget.height; double? realHeight = widget.height;
switch (widget.type) { switch (widget.type) {

View File

@@ -1924,8 +1924,7 @@ List<SettingsModel> get extraSettings => [
), ),
SettingsModel( SettingsModel(
settingsType: SettingsType.sw1tch, settingsType: SettingsType.sw1tch,
title: '视频进度条缩略图', title: '滑动跳转预览视频缩略图',
subtitle: '滑动进度条时显示视频缩略图',
leading: Icon(Icons.preview_outlined), leading: Icon(Icons.preview_outlined),
setKey: SettingBoxKey.showSeekPreview, setKey: SettingBoxKey.showSeekPreview,
defaultVal: true, defaultVal: true,

View File

@@ -512,7 +512,7 @@ class PlPlayerController {
if (showSeekPreview) { if (showSeekPreview) {
videoShot = null; videoShot = null;
showPreview.value = false; showPreview.value = false;
localPosition.value = Offset.zero; previewDx.value = 0;
} }
if (_videoPlayerController != null && if (_videoPlayerController != null &&
@@ -1575,7 +1575,7 @@ class PlPlayerController {
late bool _isQueryingVideoShot = false; late bool _isQueryingVideoShot = false;
Map? videoShot; Map? videoShot;
late final RxBool showPreview = false.obs; late final RxBool showPreview = false.obs;
late final Rx<Offset> localPosition = Offset.zero.obs; late final RxDouble previewDx = 0.0.obs;
void getVideoShot() async { void getVideoShot() async {
if (_isQueryingVideoShot) { if (_isQueryingVideoShot) {

View File

@@ -688,6 +688,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
if (_gestureType == 'horizontal') { if (_gestureType == 'horizontal') {
// live模式下禁用 // live模式下禁用
if (plPlayerController.videoType.value == 'live') return; if (plPlayerController.videoType.value == 'live') return;
final int curSliderPosition = final int curSliderPosition =
plPlayerController.sliderPosition.value.inMilliseconds; plPlayerController.sliderPosition.value.inMilliseconds;
final double scale = 90000 / renderBox.size.width; final double scale = 90000 / renderBox.size.width;
@@ -698,6 +699,16 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
pos.clamp(Duration.zero, plPlayerController.duration.value); pos.clamp(Duration.zero, plPlayerController.duration.value);
plPlayerController.onUpdatedSliderProgress(result); plPlayerController.onUpdatedSliderProgress(result);
plPlayerController.onChangedSliderStart(); plPlayerController.onChangedSliderStart();
if (plPlayerController.showSeekPreview) {
try {
plPlayerController.previewDx.value = result.inMilliseconds /
plPlayerController.duration.value.inMilliseconds *
context.size!.width;
if (plPlayerController.showPreview.value.not) {
plPlayerController.showPreview.value = true;
}
} catch (_) {}
}
} else if (_gestureType == 'left') { } else if (_gestureType == 'left') {
// 左边区域 👈 // 左边区域 👈
final double level = renderBox.size.height * 3; final double level = renderBox.size.height * 3;
@@ -744,6 +755,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
} }
}, },
onInteractionEnd: (ScaleEndDetails details) { onInteractionEnd: (ScaleEndDetails details) {
if (plPlayerController.showSeekPreview) {
plPlayerController.showPreview.value = false;
}
if (plPlayerController.isSliderMoving.value) { if (plPlayerController.isSliderMoving.value) {
plPlayerController.onChangedSliderEnd(); plPlayerController.onChangedSliderEnd();
plPlayerController.seekTo( plPlayerController.seekTo(
@@ -1036,8 +1050,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
), ),
), ),
const Spacer(), const Spacer(),
ClipRect( if (plPlayerController.showControls.value)
child: AppBarAni( AppBarAni(
controller: animationController, controller: animationController,
visible: !plPlayerController.controlsLock.value && visible: !plPlayerController.controlsLock.value &&
plPlayerController.showControls.value, plPlayerController.showControls.value,
@@ -1048,7 +1062,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
buildBottomControl: buildBottomControl(), buildBottomControl: buildBottomControl(),
), ),
), ),
),
], ],
), ),
), ),
@@ -1061,27 +1074,27 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
final int max = plPlayerController.durationSeconds.value; final int max = plPlayerController.durationSeconds.value;
final int buffer = plPlayerController.bufferedSeconds.value; final int buffer = plPlayerController.bufferedSeconds.value;
if (plPlayerController.showControls.value) { if (plPlayerController.showControls.value) {
return Container(); return const SizedBox.shrink();
} }
if (defaultBtmProgressBehavior == if (defaultBtmProgressBehavior ==
BtmProgressBehavior.alwaysHide.code) { BtmProgressBehavior.alwaysHide.code) {
return const SizedBox(); return const SizedBox.shrink();
} }
if (defaultBtmProgressBehavior == if (defaultBtmProgressBehavior ==
BtmProgressBehavior.onlyShowFullScreen.code && BtmProgressBehavior.onlyShowFullScreen.code &&
!isFullScreen) { !isFullScreen) {
return const SizedBox(); return const SizedBox.shrink();
} else if (defaultBtmProgressBehavior == } else if (defaultBtmProgressBehavior ==
BtmProgressBehavior.onlyHideFullScreen.code && BtmProgressBehavior.onlyHideFullScreen.code &&
isFullScreen) { isFullScreen) {
return const SizedBox(); return const SizedBox.shrink();
} }
if (plPlayerController.videoType.value == 'live') { if (plPlayerController.videoType.value == 'live') {
return Container(); return const SizedBox.shrink();
} }
if (value > max || max <= 0) { if (value > max || max <= 0) {
return const SizedBox(); return const SizedBox.shrink();
} }
return Positioned( return Positioned(
bottom: -1, bottom: -1,
@@ -1147,14 +1160,18 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
onDragUpdate: (duration) { onDragUpdate: (duration) {
plPlayerController plPlayerController
.onUpdatedSliderProgress(duration.timeStamp); .onUpdatedSliderProgress(duration.timeStamp);
if (plPlayerController.showPreview.value.not) { if (plPlayerController.showSeekPreview) {
plPlayerController.showPreview.value = true; if (plPlayerController.showPreview.value.not) {
plPlayerController.showPreview.value = true;
}
plPlayerController.previewDx.value =
duration.localPosition.dx;
} }
plPlayerController.localPosition.value =
duration.localPosition;
}, },
onSeek: (duration) { onSeek: (duration) {
plPlayerController.showPreview.value = false; if (plPlayerController.showSeekPreview) {
plPlayerController.showPreview.value = false;
}
plPlayerController.onChangedSliderEnd(); plPlayerController.onChangedSliderEnd();
plPlayerController plPlayerController
.onChangedSlider(duration.inSeconds.toDouble()); .onChangedSlider(duration.inSeconds.toDouble());
@@ -1374,7 +1391,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
]), ]),
))); )));
} else { } else {
return const SizedBox(); return const SizedBox.shrink();
} }
}), }),
@@ -1424,7 +1441,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
}, },
), ),
) )
: const SizedBox(), : const SizedBox.shrink(),
), ),
const Spacer(), const Spacer(),
// Expanded( // Expanded(
@@ -1470,7 +1487,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
}, },
), ),
) )
: const SizedBox(), : const SizedBox.shrink(),
), ),
], ],
), ),
@@ -1486,38 +1503,42 @@ Widget buildSeekPreviewWidget(PlPlayerController plPlayerController) {
return Obx(() { return Obx(() {
if (plPlayerController.showPreview.value.not) { if (plPlayerController.showPreview.value.not) {
return SizedBox.shrink( return SizedBox.shrink(
key: ValueKey(plPlayerController.localPosition.value), key: ValueKey(plPlayerController.previewDx.value),
); );
} }
if (plPlayerController.videoShot == null) { if (plPlayerController.videoShot == null) {
plPlayerController.getVideoShot(); plPlayerController.getVideoShot();
return SizedBox.shrink( return SizedBox.shrink(
key: ValueKey(plPlayerController.localPosition.value), key: ValueKey(plPlayerController.previewDx.value),
); );
} else if (plPlayerController.videoShot!['status'] == false) { } else if (plPlayerController.videoShot!['status'] == false) {
return SizedBox.shrink( return SizedBox.shrink(
key: ValueKey(plPlayerController.localPosition.value), key: ValueKey(plPlayerController.previewDx.value),
); );
} }
return LayoutBuilder( return LayoutBuilder(
key: ValueKey(plPlayerController.localPosition.value), key: ValueKey(plPlayerController.previewDx.value),
builder: (context, constraints) { builder: (context, constraints) {
try { try {
double scale = 1.5; double scale = plPlayerController.isFullScreen.value &&
plPlayerController.direction.value == 'horizontal'
? 4
: 2.5;
// offset // offset
double left = double left = (plPlayerController.previewDx.value - 48 * scale / 2)
(plPlayerController.localPosition.value.dx - 48 * scale / 2) .clamp(8, constraints.maxWidth - 48 * scale - 8);
.clamp(8, constraints.maxWidth - 48 * scale - 8);
// index // index
// int index = plPlayerController.sliderPositionSeconds.value ~/ 5; // int index = plPlayerController.sliderPositionSeconds.value ~/ 5;
int index = (List<int>.from( int index = max(
plPlayerController.videoShot!['data']['index']) 0,
.where((item) => (List<int>.from(plPlayerController.videoShot!['data']['index'])
item <= plPlayerController.sliderPositionSeconds.value) .where((item) =>
.length - item <=
2); plPlayerController.sliderPositionSeconds.value)
.length -
2));
// pageIndex // pageIndex
int pageIndex = (index ~/ 100).clamp( int pageIndex = (index ~/ 100).clamp(
@@ -1545,22 +1566,26 @@ Widget buildSeekPreviewWidget(PlPlayerController plPlayerController) {
return Container( return Container(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: left), padding: EdgeInsets.only(left: left),
child: SizedBox( child: UnconstrainedBox(
width: 48 * scale, child: ClipRRect(
height: 27 * scale, // clipBehavior: Clip.antiAlias,
child: UnconstrainedBox( // decoration: BoxDecoration(
child: ClipRRect( // border: Border.all(
borderRadius: BorderRadius.circular(4), // color: Colors.white,
child: Align( // strokeAlign: BorderSide.strokeAlignOutside,
widthFactor: 0.1, // ),
heightFactor: 0.1, // borderRadius: BorderRadius.circular(scale == 2.5 ? 6 : 10),
alignment: alignment, // ),
child: CachedNetworkImage( borderRadius: BorderRadius.circular(scale == 2.5 ? 6 : 10),
width: 480 * scale, child: Align(
height: 270 * scale, widthFactor: 0.1,
imageUrl: parseUrl(plPlayerController.videoShot!['data'] heightFactor: 0.1,
['image'][pageIndex]), alignment: alignment,
), child: CachedNetworkImage(
width: 480 * scale,
height: 270 * scale,
imageUrl: parseUrl(plPlayerController.videoShot!['data']
['image'][pageIndex]),
), ),
), ),
), ),
@@ -1569,7 +1594,7 @@ Widget buildSeekPreviewWidget(PlPlayerController plPlayerController) {
} catch (e) { } catch (e) {
debugPrint('seek preview: $e'); debugPrint('seek preview: $e');
return SizedBox.shrink( return SizedBox.shrink(
key: ValueKey(plPlayerController.localPosition.value), key: ValueKey(plPlayerController.previewDx.value),
); );
} }
}); });

View File

@@ -30,9 +30,7 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
//阅读器限制 //阅读器限制
Timer? accessibilityDebounce; Timer? accessibilityDebounce;
double lastAnnouncedValue = -1; double lastAnnouncedValue = -1;
return Container( return Padding(
color: Colors.transparent,
height: 120,
padding: const EdgeInsets.symmetric(horizontal: 10), padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
@@ -104,11 +102,13 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
onDragUpdate: (duration) { onDragUpdate: (duration) {
double newProgress = double newProgress =
duration.timeStamp.inSeconds / max; duration.timeStamp.inSeconds / max;
if (controller!.showPreview.value.not) { if (controller!.showSeekPreview) {
controller!.showPreview.value = true; if (controller!.showPreview.value.not) {
controller!.showPreview.value = true;
}
controller!.previewDx.value =
duration.localPosition.dx;
} }
controller!.localPosition.value =
duration.localPosition;
if ((newProgress - lastAnnouncedValue).abs() > 0.02) { if ((newProgress - lastAnnouncedValue).abs() > 0.02) {
accessibilityDebounce?.cancel(); accessibilityDebounce?.cancel();
accessibilityDebounce = accessibilityDebounce =
@@ -123,7 +123,9 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
.onUpdatedSliderProgress(duration.timeStamp); .onUpdatedSliderProgress(duration.timeStamp);
}, },
onSeek: (duration) { onSeek: (duration) {
controller!.showPreview.value = false; if (controller!.showSeekPreview) {
controller!.showPreview.value = false;
}
controller!.onChangedSliderEnd(); controller!.onChangedSliderEnd();
controller! controller!
.onChangedSlider(duration.inSeconds.toDouble()); .onChangedSlider(duration.inSeconds.toDouble());
@@ -168,7 +170,7 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
Positioned( Positioned(
left: 0, left: 0,
right: 0, right: 0,
bottom: 16, bottom: 18,
child: buildSeekPreviewWidget(controller!), child: buildSeekPreviewWidget(controller!),
), ),
], ],