fix:完善视频日志、操作记录页面功能

This commit is contained in:
liyi 2025-01-10 14:46:51 +08:00
parent 6ecf89088e
commit 50d7ea78d9
10 changed files with 491 additions and 257 deletions

View File

@ -248,6 +248,7 @@ class DoorLockLogLogic extends BaseGetXController {
///
StreamSubscription? _getDoorLockLogListRefreshUIEvent;
void _getDoorLockLogListRefreshUIAction() {
_getDoorLockLogListRefreshUIEvent = eventBus
.on<DoorLockLogListRefreshUI>()

View File

@ -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_state.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/advancedCalendar/src/widget.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/noData.dart';
import 'package:star_lock/tools/showCupertinoAlertView.dart';
@ -142,6 +146,7 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
);
}
}
// switch (value) {
// case "读取记录".tr:
// {
@ -256,31 +261,24 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
color: Colors.white,
borderRadius: BorderRadius.circular(16.w),
),
child: Obx(() => EasyRefreshTool(
onRefresh: () async {
logic.mockNetworkDataRequest(isRefresh: true);
},
onLoad: () async {
logic.mockNetworkDataRequest(isRefresh: false);
},
child: state.lockLogItemList.isNotEmpty
? Timeline.tileBuilder(
builder: _timelineBuilderWidget(),
theme: TimelineThemeData(
nodePosition: 0.04, //
connectorTheme: const ConnectorThemeData(
thickness: 1.0,
color: AppColors.greyLineColor,
indent: 0.5,
),
indicatorTheme: const IndicatorThemeData(
size: 8.0,
color: AppColors.greyLineColor,
position: 0.4,
),
),
)
: NoData())),
child: Obx(() => state.lockLogItemList.isNotEmpty
? Timeline.tileBuilder(
builder: _timelineBuilderWidget(),
theme: TimelineThemeData(
nodePosition: 0.04, //
connectorTheme: const ConnectorThemeData(
thickness: 1.0,
color: AppColors.greyLineColor,
indent: 0.5,
),
indicatorTheme: const IndicatorThemeData(
size: 8.0,
color: AppColors.greyLineColor,
position: 0.4,
),
),
)
: NoData()),
);
}
@ -319,33 +317,13 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
SizedBox(
height: 10.h,
),
GestureDetector(
onTap: () {
Get.toNamed(Routers.videoLogDetailPage);
},
child: Stack(
children: <Widget>[
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,
),
),
],
),
),
videoItem(RecordListData(
recordId: state.lockLogItemList.value[index].recordId,
recordType: state.lockLogItemList.value[index].recordType,
operateDate: state.lockLogItemList.value[index].operateDate,
imagesUrl: state.lockLogItemList.value[index].imagesUrl,
videoUrl: state.lockLogItemList.value[index].videoUrl,
)),
SizedBox(
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
void didChangeDependencies() {
super.didChangeDependencies();

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:star_lock/network/api_repository.dart';
import 'package:star_lock/tools/baseGetXController.dart';
import 'package:video_thumbnail/video_thumbnail.dart';
import 'videoLog_state.dart';
class VideoLogLogic extends BaseGetXController {
@ -14,33 +14,10 @@ class VideoLogLogic extends BaseGetXController {
);
if (entity.errorCode!.codeIsSuccessful) {
state.videoLogList.value = entity.data!;
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
onReady() {
super.onReady();

View File

@ -5,12 +5,15 @@ import 'package:star_lock/appRouters.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_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/noData.dart';
import 'package:video_player/video_player.dart';
import '../../../../app_settings/app_colors.dart';
import '../../../../tools/titleAppBar.dart';
import 'videoLog_logic.dart';import 'package:video_thumbnail/video_thumbnail.dart';
import 'videoLog_logic.dart';
class VideoLogPage extends StatefulWidget {
const VideoLogPage({Key? key}) : super(key: key);
@ -23,6 +26,12 @@ class _VideoLogPageState extends State<VideoLogPage> {
final VideoLogLogic logic = Get.put(VideoLogLogic());
final VideoLogState state = Get.find<VideoLogLogic>().state;
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -275,10 +284,22 @@ class _VideoLogPageState extends State<VideoLogPage> {
Widget videoItem(RecordListData recordData) {
return GestureDetector(
onTap: () {
Get.toNamed(Routers.videoLogDetailPage, arguments: <String, Object>{
'recordData': recordData,
'videoDataList': state.videoLogList.value
});
if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) {
Get.toNamed(Routers.videoLogDetailPage, arguments: <String, Object>{
'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(
width: itemW,
@ -314,15 +335,8 @@ class _VideoLogPageState extends State<VideoLogPage> {
}
}
_buildVideoItem(RecordListData recordData) async {
final uint8list = await VideoThumbnail.thumbnailData(
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!);
_buildVideoItem(RecordListData recordData) {
return VideoThumbnail(videoUrl: recordData.videoUrl!);
}
_buildImageItem(RecordListData recordData) {

View File

@ -1,10 +1,11 @@
import 'package:flutter/material.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/tools/dateTool.dart';
import 'package:video_player/video_player.dart';
class ControlsOverlay extends StatelessWidget {
class ControlsOverlay extends StatefulWidget {
const ControlsOverlay(
{Key? key, required this.controller, required this.recordData})
: super(key: key);
@ -34,6 +35,17 @@ class ControlsOverlay extends StatelessWidget {
final VideoPlayerController controller;
final RecordListData recordData;
@override
State<ControlsOverlay> createState() => _ControlsOverlayState();
}
class _ControlsOverlayState extends State<ControlsOverlay> {
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
return Stack(
@ -41,7 +53,7 @@ class ControlsOverlay extends StatelessWidget {
AnimatedSwitcher(
duration: const Duration(milliseconds: 50),
reverseDuration: const Duration(milliseconds: 200),
child: controller.value.isPlaying
child: widget.controller.value.isPlaying
? const SizedBox.shrink()
: Container(
color: Colors.black26,
@ -59,7 +71,10 @@ class ControlsOverlay extends StatelessWidget {
),
GestureDetector(
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,
children: [
Text(
DateTool()
.dateToYMDHNString(recordData.operateDate.toString()),
DateTool().dateToYMDHNString(
widget.recordData.operateDate.toString()),
style: TextStyle(color: Colors.white, fontSize: 20.sp)),
Expanded(child: SizedBox(width: 10.w)),
Container(
@ -187,7 +202,7 @@ class ControlsOverlay extends StatelessWidget {
children: [
//
InkWell(
child: controller.value.isPlaying
child: widget.controller.value.isPlaying
? const Icon(Icons.pause,
size: 30, color: Color(0xffefefef))
: const Icon(Icons.play_arrow,
@ -196,14 +211,14 @@ class ControlsOverlay extends StatelessWidget {
// if(controller.value.isBuffering == false){
// return;
// }
controller.value.isPlaying
? controller.pause()
: controller.play();
widget.controller.value.isPlaying
? widget.controller.pause()
: widget.controller.play();
},
),
//
Text(
formatString(controller.value.position),
formatString(widget.controller.value.position),
style: TextStyle(
fontSize: 22.sp, color: const Color(0xffefefef)),
),
@ -211,19 +226,20 @@ class ControlsOverlay extends StatelessWidget {
Expanded(
child: Slider(
activeColor: const Color(0xFFFFFFFF),
max: controller.value.duration.inMilliseconds
max: widget.controller.value.duration.inMilliseconds
.truncateToDouble(),
value: controller.value.position.inMilliseconds
value: widget.controller.value.position.inMilliseconds
.truncateToDouble(),
onChanged: (newRating) {
controller
widget.controller
.seekTo(Duration(milliseconds: newRating.truncate()));
setState(() {});
},
),
),
//
Text(
formatString(controller.value.duration),
formatString(widget.controller.value.duration),
style: TextStyle(
fontSize: 22.sp, color: const Color(0xffefefef)),
),

View File

@ -1,10 +1,13 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.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/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:video_player/video_player.dart';
@ -39,6 +42,23 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
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
Widget build(BuildContext context) {
return Scaffold(
@ -48,170 +68,111 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
haveBack: true,
backgroundColor: AppColors.mainColor,
),
body: Column(
children: <Widget>[
AspectRatio(
aspectRatio: state.videoController.value.aspectRatio,
child: Stack(
alignment: Alignment.bottomCenter,
body: state.videoController.value.isInitialized
? Column(
children: <Widget>[
VideoPlayer(state.videoController),
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),
],
),
),
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(
Container(
height: 500.h,
decoration: BoxDecoration(color: Colors.black),
child: AspectRatio(
aspectRatio: 16 / 9,
child: Stack(
alignment: Alignment.bottomCenter,
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)
FittedBox(
child: SizedBox(
width: state.videoController.value.size.width,
height: state.videoController.value.size.height,
child: VideoPlayer(state.videoController),
),
fit: BoxFit.cover,
),
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(
// itemCount: state.videoLogList.value.length,
// itemBuilder: (c, index) {
// return Column(
// children: [
// 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()
// ],
// );
// }),
// ),
],
),
),
),
),
_buildOther(),
],
)
: Center(
child: CircularProgressIndicator(),
),
);
}
double itemW = (1.sw - 15.w * 4) / 3;
double itemH = (1.sw - 15.w * 4) / 3 + 40.h;
Widget mainListView(int index, CloudStorageData itemData) {
return GridView.builder(
padding: EdgeInsets.only(left: 15.w, right: 15.w),
itemCount: itemData.recordList!.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
//
crossAxisCount: 3,
//
mainAxisSpacing: 10.w,
//
crossAxisSpacing: 15.w,
//
childAspectRatio: itemW / itemH),
itemBuilder: (BuildContext context, int index) {
return videoItem(false, itemData.recordList![index]);
Widget videoItem(RecordListData recordData) {
return GestureDetector(
onTap: () {
if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) {
state.recordData.value = recordData;
_initializeVideoPlayer(recordData.videoUrl!);
setState(() {});
} else if (recordData.imagesUrl != null &&
recordData.imagesUrl!.isNotEmpty) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FullScreenImagePage(
imageUrl: recordData.imagesUrl!,
),
),
);
}
},
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) {
return SizedBox(
width: itemW,
height: itemH,
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(10.w),
child: Stack(children: <Widget>[
Container(
width: itemW,
height: itemW,
margin: const EdgeInsets.all(0),
color: Colors.white,
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))
],
),
],
),
_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,
);
}
@ -225,4 +186,48 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
super.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);
}
}

View File

@ -16,5 +16,6 @@ class VideoLogDetailState {
if (map['videoDataList'] != null) {
videoLogList.value = map['videoDataList'];
}
print('object');
}
}

View File

@ -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),
),
),
);
}
}

View 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, // /退
),
],
),
);
}
}

View 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)),
],
),
);
}
}