mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
opt: video bottom control
Closes #244 Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -3,13 +3,11 @@ enum BottomControlType {
|
|||||||
playOrPause,
|
playOrPause,
|
||||||
next,
|
next,
|
||||||
time,
|
time,
|
||||||
space,
|
|
||||||
episode,
|
episode,
|
||||||
fit,
|
fit,
|
||||||
subtitle,
|
subtitle,
|
||||||
speed,
|
speed,
|
||||||
fullscreen,
|
fullscreen,
|
||||||
custom,
|
|
||||||
viewPoints,
|
viewPoints,
|
||||||
superResolution,
|
superResolution,
|
||||||
dmChart,
|
dmChart,
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ class PLVideoPlayer extends StatefulWidget {
|
|||||||
this.headerControl,
|
this.headerControl,
|
||||||
this.bottomControl,
|
this.bottomControl,
|
||||||
this.danmuWidget,
|
this.danmuWidget,
|
||||||
this.bottomList,
|
|
||||||
this.customWidget,
|
this.customWidget,
|
||||||
this.customWidgets,
|
this.customWidgets,
|
||||||
this.showEpisodes,
|
this.showEpisodes,
|
||||||
@@ -63,7 +62,6 @@ class PLVideoPlayer extends StatefulWidget {
|
|||||||
final PreferredSizeWidget? headerControl;
|
final PreferredSizeWidget? headerControl;
|
||||||
final PreferredSizeWidget? bottomControl;
|
final PreferredSizeWidget? bottomControl;
|
||||||
final Widget? danmuWidget;
|
final Widget? danmuWidget;
|
||||||
final List<BottomControlType>? bottomList;
|
|
||||||
// List<Widget> or Widget
|
// List<Widget> or Widget
|
||||||
|
|
||||||
final Widget? customWidget;
|
final Widget? customWidget;
|
||||||
@@ -251,7 +249,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 动态构建底部控制条
|
// 动态构建底部控制条
|
||||||
List<Widget> buildBottomControl() {
|
Widget buildBottomControl() {
|
||||||
bool isSeason = videoIntroController?.videoDetail.value.ugcSeason != null;
|
bool isSeason = videoIntroController?.videoDetail.value.ugcSeason != null;
|
||||||
bool isPage = videoIntroController?.videoDetail.value.pages != null &&
|
bool isPage = videoIntroController?.videoDetail.value.pages != null &&
|
||||||
videoIntroController!.videoDetail.value.pages!.length > 1;
|
videoIntroController!.videoDetail.value.pages!.length > 1;
|
||||||
@@ -321,7 +319,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
|
|
||||||
/// 时间进度
|
/// 时间进度
|
||||||
BottomControlType.time: Column(
|
BottomControlType.time: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// 播放时间
|
// 播放时间
|
||||||
Obx(() {
|
Obx(() {
|
||||||
@@ -354,9 +352,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
/// 空白占位
|
|
||||||
BottomControlType.space: const Spacer(),
|
|
||||||
|
|
||||||
/// 高能进度条
|
/// 高能进度条
|
||||||
BottomControlType.dmChart: Obx(() => plPlayerController.dmTrend.isEmpty
|
BottomControlType.dmChart: Obx(() => plPlayerController.dmTrend.isEmpty
|
||||||
? const SizedBox.shrink()
|
? const SizedBox.shrink()
|
||||||
@@ -622,48 +617,61 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
BottomControlType.fullscreen: SizedBox(
|
BottomControlType.fullscreen: SizedBox(
|
||||||
width: widgetWidth,
|
width: widgetWidth,
|
||||||
height: 30,
|
height: 30,
|
||||||
child: Obx(() => ComBtn(
|
child: Obx(
|
||||||
icon: Icon(
|
() => ComBtn(
|
||||||
isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen,
|
icon: Icon(
|
||||||
semanticLabel: isFullScreen ? '退出全屏' : '全屏',
|
isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen,
|
||||||
size: 24,
|
semanticLabel: isFullScreen ? '退出全屏' : '全屏',
|
||||||
color: Colors.white,
|
size: 24,
|
||||||
),
|
color: Colors.white,
|
||||||
fuc: () =>
|
),
|
||||||
plPlayerController.triggerFullScreen(status: !isFullScreen),
|
fuc: () =>
|
||||||
)),
|
plPlayerController.triggerFullScreen(status: !isFullScreen),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
final List<Widget> list = [];
|
|
||||||
List<BottomControlType> userSpecifyItem = widget.bottomList ??
|
List<BottomControlType> userSpecifyItemLeft = [
|
||||||
[
|
BottomControlType.playOrPause,
|
||||||
BottomControlType.playOrPause,
|
BottomControlType.time,
|
||||||
BottomControlType.time,
|
if (anySeason) BottomControlType.pre,
|
||||||
if (anySeason) BottomControlType.pre,
|
if (anySeason) BottomControlType.next,
|
||||||
if (anySeason) BottomControlType.next,
|
];
|
||||||
BottomControlType.space,
|
|
||||||
BottomControlType.dmChart,
|
List<BottomControlType> userSpecifyItemRight = [
|
||||||
BottomControlType.superResolution,
|
BottomControlType.dmChart,
|
||||||
BottomControlType.viewPoints,
|
BottomControlType.superResolution,
|
||||||
if (anySeason) BottomControlType.episode,
|
BottomControlType.viewPoints,
|
||||||
if (isFullScreen) BottomControlType.fit,
|
if (anySeason) BottomControlType.episode,
|
||||||
BottomControlType.subtitle,
|
if (isFullScreen) BottomControlType.fit,
|
||||||
BottomControlType.speed,
|
BottomControlType.subtitle,
|
||||||
BottomControlType.fullscreen,
|
BottomControlType.speed,
|
||||||
];
|
BottomControlType.fullscreen,
|
||||||
for (var i = 0; i < userSpecifyItem.length; i++) {
|
];
|
||||||
if (userSpecifyItem[i] == BottomControlType.custom) {
|
|
||||||
if (widget.customWidget != null && widget.customWidget is Widget) {
|
return Row(
|
||||||
list.add(widget.customWidget!);
|
children: [
|
||||||
}
|
...userSpecifyItemLeft.map((item) => videoProgressWidgets[item]!),
|
||||||
if (widget.customWidgets != null && widget.customWidgets!.isNotEmpty) {
|
Expanded(
|
||||||
list.addAll(widget.customWidgets!);
|
child: LayoutBuilder(
|
||||||
}
|
builder: (context, constraints) => FittedBox(
|
||||||
} else {
|
child: ConstrainedBox(
|
||||||
list.add(videoProgressWidgets[userSpecifyItem[i]]!);
|
constraints: BoxConstraints(
|
||||||
}
|
minWidth: constraints.maxWidth,
|
||||||
}
|
),
|
||||||
return list;
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: userSpecifyItemRight
|
||||||
|
.map((item) => videoProgressWidgets[item]!)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlPlayerController get plPlayerController => widget.plPlayerController;
|
PlPlayerController get plPlayerController => widget.plPlayerController;
|
||||||
@@ -1101,34 +1109,36 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
|
|
||||||
// 头部、底部控制条
|
// 头部、底部控制条
|
||||||
Obx(
|
Obx(
|
||||||
() => Column(
|
() => Positioned.fill(
|
||||||
children: [
|
child: Column(
|
||||||
if (widget.headerControl != null ||
|
children: [
|
||||||
plPlayerController.headerControl != null)
|
if (widget.headerControl != null ||
|
||||||
ClipRect(
|
plPlayerController.headerControl != null)
|
||||||
child: AppBarAni(
|
ClipRect(
|
||||||
|
child: AppBarAni(
|
||||||
|
controller: animationController,
|
||||||
|
visible: !plPlayerController.controlsLock.value &&
|
||||||
|
plPlayerController.showControls.value,
|
||||||
|
position: 'top',
|
||||||
|
child: widget.headerControl ??
|
||||||
|
plPlayerController.headerControl!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if (plPlayerController.showControls.value)
|
||||||
|
AppBarAni(
|
||||||
controller: animationController,
|
controller: animationController,
|
||||||
visible: !plPlayerController.controlsLock.value &&
|
visible: !plPlayerController.controlsLock.value &&
|
||||||
plPlayerController.showControls.value,
|
plPlayerController.showControls.value,
|
||||||
position: 'top',
|
position: 'bottom',
|
||||||
child: widget.headerControl ??
|
child: widget.bottomControl ??
|
||||||
plPlayerController.headerControl!,
|
BottomControl(
|
||||||
|
controller: plPlayerController,
|
||||||
|
buildBottomControl: buildBottomControl,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
const Spacer(),
|
),
|
||||||
if (plPlayerController.showControls.value)
|
|
||||||
AppBarAni(
|
|
||||||
controller: animationController,
|
|
||||||
visible: !plPlayerController.controlsLock.value &&
|
|
||||||
plPlayerController.showControls.value,
|
|
||||||
position: 'bottom',
|
|
||||||
child: widget.bottomControl ??
|
|
||||||
BottomControl(
|
|
||||||
controller: plPlayerController,
|
|
||||||
buildBottomControl: buildBottomControl(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ import 'package:PiliPlus/utils/feed_back.dart';
|
|||||||
import '../../../common/widgets/audio_video_progress_bar.dart';
|
import '../../../common/widgets/audio_video_progress_bar.dart';
|
||||||
|
|
||||||
class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||||
final PlPlayerController? controller;
|
final PlPlayerController controller;
|
||||||
final List<Widget>? buildBottomControl;
|
final Function buildBottomControl;
|
||||||
const BottomControl({
|
const BottomControl({
|
||||||
this.controller,
|
required this.controller,
|
||||||
this.buildBottomControl,
|
required this.buildBottomControl,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -37,13 +37,13 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Obx(
|
Obx(
|
||||||
() {
|
() {
|
||||||
final int value = controller!.sliderPositionSeconds.value;
|
final int value = controller.sliderPositionSeconds.value;
|
||||||
final int max = controller!.durationSeconds.value.inSeconds;
|
final int max = controller.durationSeconds.value.inSeconds;
|
||||||
final int buffer = controller!.bufferedSeconds.value;
|
final int buffer = controller.bufferedSeconds.value;
|
||||||
if (value > max || max <= 0) {
|
if (value > max || max <= 0) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
@@ -57,12 +57,12 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
children: [
|
children: [
|
||||||
if (controller?.dmTrend.isNotEmpty == true &&
|
if (controller.dmTrend.isNotEmpty &&
|
||||||
controller?.showDmChart.value == true)
|
controller.showDmChart.value)
|
||||||
buildDmChart(context, controller!, 4.5),
|
buildDmChart(context, controller, 4.5),
|
||||||
if (controller?.viewPointList.isNotEmpty == true &&
|
if (controller.viewPointList.isNotEmpty &&
|
||||||
controller?.showVP.value == true)
|
controller.showVP.value)
|
||||||
buildViewPointWidget(controller!, 8.75),
|
buildViewPointWidget(controller, 8.75),
|
||||||
ProgressBar(
|
ProgressBar(
|
||||||
progress: Duration(seconds: value),
|
progress: Duration(seconds: value),
|
||||||
buffered: Duration(seconds: buffer),
|
buffered: Duration(seconds: buffer),
|
||||||
@@ -76,16 +76,16 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
thumbRadius: 7,
|
thumbRadius: 7,
|
||||||
onDragStart: (duration) {
|
onDragStart: (duration) {
|
||||||
feedBack();
|
feedBack();
|
||||||
controller!.onChangedSliderStart();
|
controller.onChangedSliderStart();
|
||||||
},
|
},
|
||||||
onDragUpdate: (duration) {
|
onDragUpdate: (duration) {
|
||||||
double newProgress =
|
double newProgress =
|
||||||
duration.timeStamp.inSeconds / max;
|
duration.timeStamp.inSeconds / max;
|
||||||
if (controller!.showSeekPreview) {
|
if (controller.showSeekPreview) {
|
||||||
if (controller!.showPreview.value.not) {
|
if (controller.showPreview.value.not) {
|
||||||
controller!.showPreview.value = true;
|
controller.showPreview.value = true;
|
||||||
}
|
}
|
||||||
controller!.previewDx.value =
|
controller.previewDx.value =
|
||||||
duration.localPosition.dx;
|
duration.localPosition.dx;
|
||||||
}
|
}
|
||||||
if ((newProgress - lastAnnouncedValue).abs() > 0.02) {
|
if ((newProgress - lastAnnouncedValue).abs() > 0.02) {
|
||||||
@@ -98,17 +98,17 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
lastAnnouncedValue = newProgress;
|
lastAnnouncedValue = newProgress;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
controller!
|
controller
|
||||||
.onUpdatedSliderProgress(duration.timeStamp);
|
.onUpdatedSliderProgress(duration.timeStamp);
|
||||||
},
|
},
|
||||||
onSeek: (duration) {
|
onSeek: (duration) {
|
||||||
if (controller!.showSeekPreview) {
|
if (controller.showSeekPreview) {
|
||||||
controller!.showPreview.value = false;
|
controller.showPreview.value = false;
|
||||||
}
|
}
|
||||||
controller!.onChangedSliderEnd();
|
controller.onChangedSliderEnd();
|
||||||
controller!
|
controller
|
||||||
.onChangedSlider(duration.inSeconds.toDouble());
|
.onChangedSlider(duration.inSeconds.toDouble());
|
||||||
controller!.seekTo(
|
controller.seekTo(
|
||||||
Duration(seconds: duration.inSeconds),
|
Duration(seconds: duration.inSeconds),
|
||||||
type: 'slider');
|
type: 'slider');
|
||||||
SemanticsService.announce(
|
SemanticsService.announce(
|
||||||
@@ -116,7 +116,7 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
TextDirection.ltr);
|
TextDirection.ltr);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (controller?.segmentList.isNotEmpty == true)
|
if (controller.segmentList.isNotEmpty)
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
@@ -125,13 +125,13 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
size: Size(double.infinity, 3.5),
|
size: Size(double.infinity, 3.5),
|
||||||
painter: SegmentProgressBar(
|
painter: SegmentProgressBar(
|
||||||
segmentColors: controller!.segmentList,
|
segmentColors: controller.segmentList,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (controller?.viewPointList.isNotEmpty == true &&
|
if (controller.viewPointList.isNotEmpty &&
|
||||||
controller?.showVP.value == true)
|
controller.showVP.value)
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
@@ -140,17 +140,17 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
size: Size(double.infinity, 3.5),
|
size: Size(double.infinity, 3.5),
|
||||||
painter: SegmentProgressBar(
|
painter: SegmentProgressBar(
|
||||||
segmentColors: controller!.viewPointList,
|
segmentColors: controller.viewPointList,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (controller?.showSeekPreview == true)
|
if (controller.showSeekPreview)
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 18,
|
bottom: 18,
|
||||||
child: buildSeekPreviewWidget(controller!),
|
child: buildSeekPreviewWidget(controller),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -158,9 +158,7 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Row(
|
buildBottomControl(),
|
||||||
children: [...buildBottomControl!],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user