fix:完善视频日志、操作记录页面功能
This commit is contained in:
parent
6ecf89088e
commit
50d7ea78d9
@ -248,6 +248,7 @@ class DoorLockLogLogic extends BaseGetXController {
|
|||||||
|
|
||||||
/// 刷新门锁日志列表
|
/// 刷新门锁日志列表
|
||||||
StreamSubscription? _getDoorLockLogListRefreshUIEvent;
|
StreamSubscription? _getDoorLockLogListRefreshUIEvent;
|
||||||
|
|
||||||
void _getDoorLockLogListRefreshUIAction() {
|
void _getDoorLockLogListRefreshUIAction() {
|
||||||
_getDoorLockLogListRefreshUIEvent = eventBus
|
_getDoorLockLogListRefreshUIEvent = eventBus
|
||||||
.on<DoorLockLogListRefreshUI>()
|
.on<DoorLockLogListRefreshUI>()
|
||||||
|
|||||||
@ -7,9 +7,13 @@ import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_entity.dart';
|
|||||||
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_logic.dart';
|
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_logic.dart';
|
||||||
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_state.dart';
|
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_state.dart';
|
||||||
import 'package:star_lock/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_page.dart';
|
import 'package:star_lock/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_page.dart';
|
||||||
|
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart';
|
||||||
|
import 'package:star_lock/main/lockDetail/videoLog/widget/full_screenImage_page.dart';
|
||||||
|
import 'package:star_lock/main/lockDetail/videoLog/widget/video_thumbnail.dart';
|
||||||
import 'package:star_lock/tools/EasyRefreshTool.dart';
|
import 'package:star_lock/tools/EasyRefreshTool.dart';
|
||||||
import 'package:star_lock/tools/advancedCalendar/src/widget.dart';
|
import 'package:star_lock/tools/advancedCalendar/src/widget.dart';
|
||||||
import 'package:star_lock/tools/commonDataManage.dart';
|
import 'package:star_lock/tools/commonDataManage.dart';
|
||||||
|
import 'package:star_lock/tools/dateTool.dart';
|
||||||
import 'package:star_lock/tools/menuItem/xsDropDownWidget.dart';
|
import 'package:star_lock/tools/menuItem/xsDropDownWidget.dart';
|
||||||
import 'package:star_lock/tools/noData.dart';
|
import 'package:star_lock/tools/noData.dart';
|
||||||
import 'package:star_lock/tools/showCupertinoAlertView.dart';
|
import 'package:star_lock/tools/showCupertinoAlertView.dart';
|
||||||
@ -142,6 +146,7 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// switch (value) {
|
// switch (value) {
|
||||||
// case "读取记录".tr:
|
// case "读取记录".tr:
|
||||||
// {
|
// {
|
||||||
@ -256,31 +261,24 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(16.w),
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
),
|
),
|
||||||
child: Obx(() => EasyRefreshTool(
|
child: Obx(() => state.lockLogItemList.isNotEmpty
|
||||||
onRefresh: () async {
|
? Timeline.tileBuilder(
|
||||||
logic.mockNetworkDataRequest(isRefresh: true);
|
builder: _timelineBuilderWidget(),
|
||||||
},
|
theme: TimelineThemeData(
|
||||||
onLoad: () async {
|
nodePosition: 0.04, //居左侧距离
|
||||||
logic.mockNetworkDataRequest(isRefresh: false);
|
connectorTheme: const ConnectorThemeData(
|
||||||
},
|
thickness: 1.0,
|
||||||
child: state.lockLogItemList.isNotEmpty
|
color: AppColors.greyLineColor,
|
||||||
? Timeline.tileBuilder(
|
indent: 0.5,
|
||||||
builder: _timelineBuilderWidget(),
|
),
|
||||||
theme: TimelineThemeData(
|
indicatorTheme: const IndicatorThemeData(
|
||||||
nodePosition: 0.04, //居左侧距离
|
size: 8.0,
|
||||||
connectorTheme: const ConnectorThemeData(
|
color: AppColors.greyLineColor,
|
||||||
thickness: 1.0,
|
position: 0.4,
|
||||||
color: AppColors.greyLineColor,
|
),
|
||||||
indent: 0.5,
|
),
|
||||||
),
|
)
|
||||||
indicatorTheme: const IndicatorThemeData(
|
: NoData()),
|
||||||
size: 8.0,
|
|
||||||
color: AppColors.greyLineColor,
|
|
||||||
position: 0.4,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: NoData())),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,33 +317,13 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 10.h,
|
height: 10.h,
|
||||||
),
|
),
|
||||||
GestureDetector(
|
videoItem(RecordListData(
|
||||||
onTap: () {
|
recordId: state.lockLogItemList.value[index].recordId,
|
||||||
Get.toNamed(Routers.videoLogDetailPage);
|
recordType: state.lockLogItemList.value[index].recordType,
|
||||||
},
|
operateDate: state.lockLogItemList.value[index].operateDate,
|
||||||
child: Stack(
|
imagesUrl: state.lockLogItemList.value[index].imagesUrl,
|
||||||
children: <Widget>[
|
videoUrl: state.lockLogItemList.value[index].videoUrl,
|
||||||
if (timelineData.imagesUrl!.isNotEmpty)
|
)),
|
||||||
Image.network(
|
|
||||||
timelineData.imagesUrl!,
|
|
||||||
width: 260.w,
|
|
||||||
height: 260.h,
|
|
||||||
)
|
|
||||||
else
|
|
||||||
Container(),
|
|
||||||
Positioned(
|
|
||||||
top: 150.h,
|
|
||||||
left: 10.w,
|
|
||||||
child: Image(
|
|
||||||
image: const AssetImage(
|
|
||||||
'images/main/icon_lockLog_play.png'),
|
|
||||||
width: 24.w,
|
|
||||||
height: 20.w,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 20.h,
|
height: 20.h,
|
||||||
),
|
),
|
||||||
@ -357,6 +335,80 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget videoItem(RecordListData recordData) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) {
|
||||||
|
final lockLogItemList = state.lockLogItemList.value;
|
||||||
|
final list = lockLogItemList
|
||||||
|
.map((e) => RecordListData(
|
||||||
|
videoUrl: e.videoUrl,
|
||||||
|
imagesUrl: e.imagesUrl,
|
||||||
|
operateDate: e.operateDate,
|
||||||
|
recordId: e.recordId,
|
||||||
|
recordType: e.recordType,
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
final selectDateString =
|
||||||
|
DateTool().dateToYMDString(state.startDate.value.toString());
|
||||||
|
final cloudStorageData =
|
||||||
|
CloudStorageData(date: selectDateString, recordList: list);
|
||||||
|
Get.toNamed(Routers.videoLogDetailPage, arguments: <String, Object>{
|
||||||
|
'recordData': recordData,
|
||||||
|
'videoDataList': [cloudStorageData]
|
||||||
|
});
|
||||||
|
} else if (recordData.imagesUrl != null &&
|
||||||
|
recordData.imagesUrl!.isNotEmpty) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => FullScreenImagePage(
|
||||||
|
imageUrl: recordData.imagesUrl!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: 260.w,
|
||||||
|
height: 260.h,
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
width: 260.w,
|
||||||
|
height: 260.h,
|
||||||
|
margin: const EdgeInsets.all(0),
|
||||||
|
color: Colors.white,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(10.w),
|
||||||
|
child: _buildImageOrVideoItem(recordData),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildImageOrVideoItem(RecordListData recordData) {
|
||||||
|
if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) {
|
||||||
|
return _buildVideoItem(recordData);
|
||||||
|
} else {
|
||||||
|
return _buildImageItem(recordData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildVideoItem(RecordListData recordData) {
|
||||||
|
return VideoThumbnail(videoUrl: recordData.videoUrl!);
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildImageItem(RecordListData recordData) {
|
||||||
|
return Image.network(
|
||||||
|
recordData.imagesUrl!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:star_lock/network/api_repository.dart';
|
import 'package:star_lock/network/api_repository.dart';
|
||||||
import 'package:star_lock/tools/baseGetXController.dart';
|
import 'package:star_lock/tools/baseGetXController.dart';
|
||||||
import 'package:video_thumbnail/video_thumbnail.dart';
|
|
||||||
import 'videoLog_state.dart';
|
import 'videoLog_state.dart';
|
||||||
|
|
||||||
class VideoLogLogic extends BaseGetXController {
|
class VideoLogLogic extends BaseGetXController {
|
||||||
@ -14,33 +14,10 @@ class VideoLogLogic extends BaseGetXController {
|
|||||||
);
|
);
|
||||||
if (entity.errorCode!.codeIsSuccessful) {
|
if (entity.errorCode!.codeIsSuccessful) {
|
||||||
state.videoLogList.value = entity.data!;
|
state.videoLogList.value = entity.data!;
|
||||||
|
|
||||||
state.videoLogList.refresh();
|
state.videoLogList.refresh();
|
||||||
// state.videoLogList.forEach((element) {
|
|
||||||
// final recordList = element.recordList;
|
|
||||||
// if (recordList != null && recordList.isNotEmpty) {
|
|
||||||
// recordList.forEach((item) {
|
|
||||||
// if (item.videoUrl != null && item.videoUrl!.isNotEmpty) {
|
|
||||||
// final coverImage = aw handleVideoCoverImage(item.videoUrl);
|
|
||||||
// state.videoCoverList.add(coverImage);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List?> handleVideoCoverImage(videoUrl) async {
|
|
||||||
final uint8list = await VideoThumbnail.thumbnailData(
|
|
||||||
video: videoUrl,
|
|
||||||
imageFormat: ImageFormat.JPEG,
|
|
||||||
maxWidth: 128,
|
|
||||||
// specify the width of the thumbnail, let the height auto-scaled to keep the source aspect ratio
|
|
||||||
quality: 25,
|
|
||||||
);
|
|
||||||
return uint8list;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
onReady() {
|
onReady() {
|
||||||
super.onReady();
|
super.onReady();
|
||||||
|
|||||||
@ -5,12 +5,15 @@ import 'package:star_lock/appRouters.dart';
|
|||||||
import 'package:star_lock/flavors.dart';
|
import 'package:star_lock/flavors.dart';
|
||||||
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart';
|
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart';
|
||||||
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_state.dart';
|
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_state.dart';
|
||||||
|
import 'package:star_lock/main/lockDetail/videoLog/widget/full_screenImage_page.dart';
|
||||||
|
import 'package:star_lock/main/lockDetail/videoLog/widget/video_thumbnail.dart';
|
||||||
import 'package:star_lock/tools/dateTool.dart';
|
import 'package:star_lock/tools/dateTool.dart';
|
||||||
import 'package:star_lock/tools/noData.dart';
|
import 'package:star_lock/tools/noData.dart';
|
||||||
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
import '../../../../app_settings/app_colors.dart';
|
import '../../../../app_settings/app_colors.dart';
|
||||||
import '../../../../tools/titleAppBar.dart';
|
import '../../../../tools/titleAppBar.dart';
|
||||||
import 'videoLog_logic.dart';import 'package:video_thumbnail/video_thumbnail.dart';
|
import 'videoLog_logic.dart';
|
||||||
|
|
||||||
class VideoLogPage extends StatefulWidget {
|
class VideoLogPage extends StatefulWidget {
|
||||||
const VideoLogPage({Key? key}) : super(key: key);
|
const VideoLogPage({Key? key}) : super(key: key);
|
||||||
@ -23,6 +26,12 @@ class _VideoLogPageState extends State<VideoLogPage> {
|
|||||||
final VideoLogLogic logic = Get.put(VideoLogLogic());
|
final VideoLogLogic logic = Get.put(VideoLogLogic());
|
||||||
final VideoLogState state = Get.find<VideoLogLogic>().state;
|
final VideoLogState state = Get.find<VideoLogLogic>().state;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
// TODO: implement initState
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -275,10 +284,22 @@ class _VideoLogPageState extends State<VideoLogPage> {
|
|||||||
Widget videoItem(RecordListData recordData) {
|
Widget videoItem(RecordListData recordData) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Get.toNamed(Routers.videoLogDetailPage, arguments: <String, Object>{
|
if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) {
|
||||||
'recordData': recordData,
|
Get.toNamed(Routers.videoLogDetailPage, arguments: <String, Object>{
|
||||||
'videoDataList': state.videoLogList.value
|
'recordData': recordData,
|
||||||
});
|
'videoDataList': state.videoLogList.value
|
||||||
|
});
|
||||||
|
} else if (recordData.imagesUrl != null &&
|
||||||
|
recordData.imagesUrl!.isNotEmpty) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => FullScreenImagePage(
|
||||||
|
imageUrl: recordData.imagesUrl!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: itemW,
|
width: itemW,
|
||||||
@ -314,15 +335,8 @@ class _VideoLogPageState extends State<VideoLogPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildVideoItem(RecordListData recordData) async {
|
_buildVideoItem(RecordListData recordData) {
|
||||||
final uint8list = await VideoThumbnail.thumbnailData(
|
return VideoThumbnail(videoUrl: recordData.videoUrl!);
|
||||||
video: recordData.videoUrl!,
|
|
||||||
imageFormat: ImageFormat.JPEG,
|
|
||||||
maxWidth: 128,
|
|
||||||
// specify the width of the thumbnail, let the height auto-scaled to keep the source aspect ratio
|
|
||||||
quality: 25,
|
|
||||||
);
|
|
||||||
return Image.memory(uint8list!);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildImageItem(RecordListData recordData) {
|
_buildImageItem(RecordListData recordData) {
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:star_lock/app_settings/app_settings.dart';
|
||||||
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart';
|
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart';
|
||||||
import 'package:star_lock/tools/dateTool.dart';
|
import 'package:star_lock/tools/dateTool.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
class ControlsOverlay extends StatelessWidget {
|
class ControlsOverlay extends StatefulWidget {
|
||||||
const ControlsOverlay(
|
const ControlsOverlay(
|
||||||
{Key? key, required this.controller, required this.recordData})
|
{Key? key, required this.controller, required this.recordData})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
@ -34,6 +35,17 @@ class ControlsOverlay extends StatelessWidget {
|
|||||||
final VideoPlayerController controller;
|
final VideoPlayerController controller;
|
||||||
final RecordListData recordData;
|
final RecordListData recordData;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ControlsOverlay> createState() => _ControlsOverlayState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ControlsOverlayState extends State<ControlsOverlay> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
// TODO: implement initState
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
@ -41,7 +53,7 @@ class ControlsOverlay extends StatelessWidget {
|
|||||||
AnimatedSwitcher(
|
AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 50),
|
duration: const Duration(milliseconds: 50),
|
||||||
reverseDuration: const Duration(milliseconds: 200),
|
reverseDuration: const Duration(milliseconds: 200),
|
||||||
child: controller.value.isPlaying
|
child: widget.controller.value.isPlaying
|
||||||
? const SizedBox.shrink()
|
? const SizedBox.shrink()
|
||||||
: Container(
|
: Container(
|
||||||
color: Colors.black26,
|
color: Colors.black26,
|
||||||
@ -59,7 +71,10 @@ class ControlsOverlay extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
controller.value.isPlaying ? controller.pause() : controller.play();
|
widget.controller.value.isPlaying
|
||||||
|
? widget.controller.pause()
|
||||||
|
: widget.controller.play();
|
||||||
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -133,8 +148,8 @@ class ControlsOverlay extends StatelessWidget {
|
|||||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
DateTool()
|
DateTool().dateToYMDHNString(
|
||||||
.dateToYMDHNString(recordData.operateDate.toString()),
|
widget.recordData.operateDate.toString()),
|
||||||
style: TextStyle(color: Colors.white, fontSize: 20.sp)),
|
style: TextStyle(color: Colors.white, fontSize: 20.sp)),
|
||||||
Expanded(child: SizedBox(width: 10.w)),
|
Expanded(child: SizedBox(width: 10.w)),
|
||||||
Container(
|
Container(
|
||||||
@ -187,7 +202,7 @@ class ControlsOverlay extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
//暂停按钮
|
//暂停按钮
|
||||||
InkWell(
|
InkWell(
|
||||||
child: controller.value.isPlaying
|
child: widget.controller.value.isPlaying
|
||||||
? const Icon(Icons.pause,
|
? const Icon(Icons.pause,
|
||||||
size: 30, color: Color(0xffefefef))
|
size: 30, color: Color(0xffefefef))
|
||||||
: const Icon(Icons.play_arrow,
|
: const Icon(Icons.play_arrow,
|
||||||
@ -196,14 +211,14 @@ class ControlsOverlay extends StatelessWidget {
|
|||||||
// if(controller.value.isBuffering == false){
|
// if(controller.value.isBuffering == false){
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
controller.value.isPlaying
|
widget.controller.value.isPlaying
|
||||||
? controller.pause()
|
? widget.controller.pause()
|
||||||
: controller.play();
|
: widget.controller.play();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
//当前播放进度
|
//当前播放进度
|
||||||
Text(
|
Text(
|
||||||
formatString(controller.value.position),
|
formatString(widget.controller.value.position),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 22.sp, color: const Color(0xffefefef)),
|
fontSize: 22.sp, color: const Color(0xffefefef)),
|
||||||
),
|
),
|
||||||
@ -211,19 +226,20 @@ class ControlsOverlay extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Slider(
|
child: Slider(
|
||||||
activeColor: const Color(0xFFFFFFFF),
|
activeColor: const Color(0xFFFFFFFF),
|
||||||
max: controller.value.duration.inMilliseconds
|
max: widget.controller.value.duration.inMilliseconds
|
||||||
.truncateToDouble(),
|
.truncateToDouble(),
|
||||||
value: controller.value.position.inMilliseconds
|
value: widget.controller.value.position.inMilliseconds
|
||||||
.truncateToDouble(),
|
.truncateToDouble(),
|
||||||
onChanged: (newRating) {
|
onChanged: (newRating) {
|
||||||
controller
|
widget.controller
|
||||||
.seekTo(Duration(milliseconds: newRating.truncate()));
|
.seekTo(Duration(milliseconds: newRating.truncate()));
|
||||||
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
//总视频进度
|
//总视频进度
|
||||||
Text(
|
Text(
|
||||||
formatString(controller.value.duration),
|
formatString(widget.controller.value.duration),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 22.sp, color: const Color(0xffefefef)),
|
fontSize: 22.sp, color: const Color(0xffefefef)),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart';
|
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart';
|
||||||
import 'package:star_lock/main/lockDetail/videoLog/videoLogDetail/controlsOverlay_page.dart';
|
import 'package:star_lock/main/lockDetail/videoLog/videoLogDetail/controlsOverlay_page.dart';
|
||||||
import 'package:star_lock/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_state.dart';
|
import 'package:star_lock/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_state.dart';
|
||||||
|
import 'package:star_lock/main/lockDetail/videoLog/widget/full_screenImage_page.dart';
|
||||||
|
import 'package:star_lock/main/lockDetail/videoLog/widget/video_thumbnail.dart';
|
||||||
import 'package:star_lock/tools/dateTool.dart';
|
import 'package:star_lock/tools/dateTool.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
@ -39,6 +42,23 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
|
|||||||
state.videoController.initialize();
|
state.videoController.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _initializeVideoPlayer(String videoUrl) async {
|
||||||
|
if (state.videoController != null) {
|
||||||
|
await state.videoController.dispose(); // 释放旧资源
|
||||||
|
}
|
||||||
|
state.videoController = VideoPlayerController.networkUrl(
|
||||||
|
Uri.parse(videoUrl),
|
||||||
|
videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 初始化完成后通知框架重新构建界面
|
||||||
|
await state.videoController.initialize();
|
||||||
|
state.videoController.addListener(() {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -48,170 +68,111 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
|
|||||||
haveBack: true,
|
haveBack: true,
|
||||||
backgroundColor: AppColors.mainColor,
|
backgroundColor: AppColors.mainColor,
|
||||||
),
|
),
|
||||||
body: Column(
|
body: state.videoController.value.isInitialized
|
||||||
children: <Widget>[
|
? Column(
|
||||||
AspectRatio(
|
|
||||||
aspectRatio: state.videoController.value.aspectRatio,
|
|
||||||
child: Stack(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
VideoPlayer(state.videoController),
|
Container(
|
||||||
ControlsOverlay(
|
height: 500.h,
|
||||||
controller: state.videoController,
|
decoration: BoxDecoration(color: Colors.black),
|
||||||
recordData: state.recordData.value,
|
child: AspectRatio(
|
||||||
),
|
aspectRatio: 16 / 9,
|
||||||
if (state.videoController.value.isPlaying ||
|
child: Stack(
|
||||||
state.videoController.value.isBuffering) Container() else VideoProgressIndicator(state.videoController,
|
alignment: Alignment.bottomCenter,
|
||||||
colors: VideoProgressColors(
|
|
||||||
playedColor: AppColors.mainColor),
|
|
||||||
allowScrubbing: true),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
margin: EdgeInsets.only(left: 20.w, top: 15.w),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
DateTool().dateToYMDString(
|
|
||||||
state.recordData.value.operateDate.toString()),
|
|
||||||
style: TextStyle(fontSize: 20.sp)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 15.h),
|
|
||||||
Row(
|
|
||||||
children: <Widget>[
|
|
||||||
videoItem(true, state.recordData.value),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
Expanded(
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: state.videoLogList.length,
|
|
||||||
itemBuilder: (BuildContext c, int index) {
|
|
||||||
CloudStorageData item = state.videoLogList[index];
|
|
||||||
return Column(
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
FittedBox(
|
||||||
margin: EdgeInsets.only(
|
child: SizedBox(
|
||||||
left: 20.w, top: 15.w, bottom: 15.w),
|
width: state.videoController.value.size.width,
|
||||||
child: Row(children: <Widget>[
|
height: state.videoController.value.size.height,
|
||||||
Text(item.date ?? '',
|
child: VideoPlayer(state.videoController),
|
||||||
style: TextStyle(fontSize: 20.sp)),
|
),
|
||||||
])),
|
fit: BoxFit.cover,
|
||||||
mainListView(index, item)
|
),
|
||||||
|
ControlsOverlay(
|
||||||
|
controller: state.videoController,
|
||||||
|
recordData: state.recordData.value,
|
||||||
|
),
|
||||||
|
if (state.videoController.value.isPlaying ||
|
||||||
|
state.videoController.value.isBuffering)
|
||||||
|
Container()
|
||||||
|
else
|
||||||
|
VideoProgressIndicator(
|
||||||
|
state.videoController,
|
||||||
|
colors: VideoProgressColors(
|
||||||
|
playedColor: AppColors.mainColor),
|
||||||
|
allowScrubbing: true,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
}))
|
),
|
||||||
// Expanded(
|
),
|
||||||
// child: ListView.builder(
|
_buildOther(),
|
||||||
// itemCount: state.videoLogList.value.length,
|
],
|
||||||
// itemBuilder: (c, index) {
|
)
|
||||||
// return Column(
|
: Center(
|
||||||
// children: [
|
child: CircularProgressIndicator(),
|
||||||
// Container(
|
),
|
||||||
// margin: EdgeInsets.only(
|
|
||||||
// left: 20.w, top: 15.w, bottom: 15.w),
|
|
||||||
// child: Row(children: [
|
|
||||||
// Text("2023.10.23",
|
|
||||||
// style: TextStyle(fontSize: 20.sp)),
|
|
||||||
// ])),
|
|
||||||
// mainListView()
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// }),
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
double itemW = (1.sw - 15.w * 4) / 3;
|
double itemW = (1.sw - 15.w * 4) / 3;
|
||||||
double itemH = (1.sw - 15.w * 4) / 3 + 40.h;
|
double itemH = (1.sw - 15.w * 4) / 3 + 40.h;
|
||||||
Widget mainListView(int index, CloudStorageData itemData) {
|
|
||||||
return GridView.builder(
|
Widget videoItem(RecordListData recordData) {
|
||||||
padding: EdgeInsets.only(left: 15.w, right: 15.w),
|
return GestureDetector(
|
||||||
itemCount: itemData.recordList!.length,
|
onTap: () {
|
||||||
shrinkWrap: true,
|
if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) {
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
state.recordData.value = recordData;
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
_initializeVideoPlayer(recordData.videoUrl!);
|
||||||
//横轴元素个数
|
setState(() {});
|
||||||
crossAxisCount: 3,
|
} else if (recordData.imagesUrl != null &&
|
||||||
//纵轴间距
|
recordData.imagesUrl!.isNotEmpty) {
|
||||||
mainAxisSpacing: 10.w,
|
Navigator.push(
|
||||||
// 横轴间距
|
context,
|
||||||
crossAxisSpacing: 15.w,
|
MaterialPageRoute(
|
||||||
//子组件宽高长度比例
|
builder: (context) => FullScreenImagePage(
|
||||||
childAspectRatio: itemW / itemH),
|
imageUrl: recordData.imagesUrl!,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
),
|
||||||
return videoItem(false, itemData.recordList![index]);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: itemW,
|
||||||
|
height: itemH,
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
width: itemW,
|
||||||
|
height: itemW,
|
||||||
|
margin: const EdgeInsets.all(0),
|
||||||
|
color: Colors.white,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(10.w),
|
||||||
|
child: _buildImageOrVideoItem(recordData),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget videoItem(bool isPlay, RecordListData itemData) {
|
_buildImageOrVideoItem(RecordListData recordData) {
|
||||||
return SizedBox(
|
if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) {
|
||||||
width: itemW,
|
return _buildVideoItem(recordData);
|
||||||
height: itemH,
|
} else {
|
||||||
child: Stack(
|
return _buildImageItem(recordData);
|
||||||
children: <Widget>[
|
}
|
||||||
Column(
|
}
|
||||||
children: <Widget>[
|
|
||||||
ClipRRect(
|
_buildVideoItem(RecordListData recordData) {
|
||||||
borderRadius: BorderRadius.circular(10.w),
|
return VideoThumbnail(videoUrl: recordData.videoUrl!);
|
||||||
child: Stack(children: <Widget>[
|
}
|
||||||
Container(
|
|
||||||
width: itemW,
|
_buildImageItem(RecordListData recordData) {
|
||||||
height: itemW,
|
return Image.network(
|
||||||
margin: const EdgeInsets.all(0),
|
recordData.imagesUrl!,
|
||||||
color: Colors.white,
|
fit: BoxFit.cover,
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(10.w),
|
|
||||||
child: Image(
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
image: Image.network(itemData.imagesUrl ??
|
|
||||||
'images/icon_video_placeholder.jpg')
|
|
||||||
.image),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
left: 8.w,
|
|
||||||
bottom: 5.h,
|
|
||||||
child: Text(
|
|
||||||
formatString(state.videoController.value.duration),
|
|
||||||
style:
|
|
||||||
TextStyle(color: Colors.grey, fontSize: 20.sp))),
|
|
||||||
Visibility(
|
|
||||||
visible: isPlay,
|
|
||||||
child: Positioned(
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
child: Container(
|
|
||||||
// padding: EdgeInsets.only(right: 10.w, left: 10.w),
|
|
||||||
color: const Color.fromRGBO(0, 0, 0, 0.3),
|
|
||||||
child: Center(
|
|
||||||
child: Text('播放中'.tr,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white, fontSize: 22.sp))),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
SizedBox(height: 5.h),
|
|
||||||
Text(
|
|
||||||
DateTool().dateToYMDHNString(itemData.operateDate.toString()),
|
|
||||||
style: TextStyle(fontSize: 18.sp))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,4 +186,48 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
state.videoController.dispose();
|
state.videoController.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_buildOther() {
|
||||||
|
return Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: state.videoLogList.length,
|
||||||
|
itemBuilder: (BuildContext c, int index) {
|
||||||
|
CloudStorageData item = state.videoLogList[index];
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.only(left: 20.w, top: 15.w, bottom: 15.w),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Text(item.date ?? '', style: TextStyle(fontSize: 20.sp)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
mainListView(index, item),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget mainListView(int index, CloudStorageData itemData) {
|
||||||
|
return GridView.builder(
|
||||||
|
itemCount: itemData.recordList!.length,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
//横轴元素个数
|
||||||
|
crossAxisCount: 3,
|
||||||
|
),
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return _buildItem(itemData.recordList![index]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildItem(itemData) {
|
||||||
|
return videoItem(itemData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,5 +16,6 @@ class VideoLogDetailState {
|
|||||||
if (map['videoDataList'] != null) {
|
if (map['videoDataList'] != null) {
|
||||||
videoLogList.value = map['videoDataList'];
|
videoLogList.value = map['videoDataList'];
|
||||||
}
|
}
|
||||||
|
print('object');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:photo_view/photo_view.dart';
|
||||||
|
import 'package:photo_view/photo_view_gallery.dart';
|
||||||
|
|
||||||
|
class FullScreenImagePage extends StatelessWidget {
|
||||||
|
final String imageUrl;
|
||||||
|
|
||||||
|
FullScreenImagePage({required this.imageUrl});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
child: PhotoView(
|
||||||
|
imageProvider: NetworkImage(imageUrl),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
75
lib/main/lockDetail/videoLog/widget/video_player_widget.dart
Normal file
75
lib/main/lockDetail/videoLog/widget/video_player_widget.dart
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart';
|
||||||
|
import 'package:star_lock/main/lockDetail/videoLog/videoLogDetail/controlsOverlay_page.dart';
|
||||||
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
|
class VideoPlayerWidget extends StatefulWidget {
|
||||||
|
final String videoUrl;
|
||||||
|
final RecordListData recordListData;
|
||||||
|
|
||||||
|
VideoPlayerWidget({required this.videoUrl, required this.recordListData});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_VideoPlayerWidgetState createState() => _VideoPlayerWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
|
||||||
|
late VideoPlayerController _controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// 初始化 VideoPlayerController
|
||||||
|
_controller = VideoPlayerController.network(widget.videoUrl)
|
||||||
|
..initialize().then((_) {
|
||||||
|
// 视频初始化完成后更新UI
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// 清理资源
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: 200, // 固定高度为200像素
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
children: <Widget>[
|
||||||
|
// 使用 FittedBox 来适配视频尺寸
|
||||||
|
Center(
|
||||||
|
child: _controller.value.isInitialized
|
||||||
|
? AspectRatio(
|
||||||
|
aspectRatio: _controller.value.aspectRatio, // 保持视频的原始宽高比
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.cover, // 确保视频填满整个区域,可能会裁剪部分内容
|
||||||
|
child: SizedBox(
|
||||||
|
width: _controller.value.size.width,
|
||||||
|
height: _controller.value.size.height,
|
||||||
|
child: VideoPlayer(_controller),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Container(), // 加载中显示转圈
|
||||||
|
),
|
||||||
|
ControlsOverlay(
|
||||||
|
controller: _controller,
|
||||||
|
recordData: widget.recordListData,
|
||||||
|
),
|
||||||
|
// 控制覆盖层组件
|
||||||
|
if (!_controller.value.isPlaying)
|
||||||
|
VideoProgressIndicator(
|
||||||
|
_controller,
|
||||||
|
colors: VideoProgressColors(playedColor: Colors.red),
|
||||||
|
allowScrubbing: true, // 允许用户拖动进度条进行快进/快退
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
73
lib/main/lockDetail/videoLog/widget/video_thumbnail.dart
Normal file
73
lib/main/lockDetail/videoLog/widget/video_thumbnail.dart
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
|
class VideoThumbnail extends StatefulWidget {
|
||||||
|
final String videoUrl;
|
||||||
|
|
||||||
|
VideoThumbnail({required this.videoUrl});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_VideoThumbnailState createState() => _VideoThumbnailState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VideoThumbnailState extends State<VideoThumbnail> {
|
||||||
|
late VideoPlayerController _controller;
|
||||||
|
late Future<void> _initializeVideoPlayerFuture;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// 创建并初始化 VideoPlayerController
|
||||||
|
_controller = VideoPlayerController.network(widget.videoUrl)
|
||||||
|
..initialize().then((_) {
|
||||||
|
// 当视频控制器初始化完成后,更新UI以便显示第一帧
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// 清理资源
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
// onTap: () {
|
||||||
|
// setState(() {
|
||||||
|
// if (_controller.value.isPlaying) {
|
||||||
|
// _controller.pause();
|
||||||
|
// } else {
|
||||||
|
// _controller.play();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
// 如果视频已经初始化,则显示视频玩家
|
||||||
|
_controller.value.isInitialized
|
||||||
|
? AspectRatio(
|
||||||
|
aspectRatio: 1 / 1, // 强制正方形比例
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.cover, // 确保视频封面铺满空间
|
||||||
|
child: SizedBox(
|
||||||
|
width: _controller.value.size.width,
|
||||||
|
height: _controller.value.size.height,
|
||||||
|
child: VideoPlayer(_controller),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Center(
|
||||||
|
child: CircularProgressIndicator(), // 加载中显示转圈
|
||||||
|
),
|
||||||
|
if (!_controller.value.isPlaying && _controller.value.isInitialized)
|
||||||
|
Icon(Icons.play_arrow_rounded,
|
||||||
|
size: 80, color: Colors.white.withOpacity(0.8)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user