video_preview_page.dart 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import 'dart:io';
  2. import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
  3. import 'package:auto_route/annotations.dart';
  4. import 'package:bot_toast/bot_toast.dart';
  5. import 'package:eitc_erm_dental_flutter/pages/view/widget/preview_operation_view.dart';
  6. import 'package:eitc_erm_dental_flutter/pages/view/widget/preview_title.dart';
  7. import 'package:flutter/material.dart';
  8. import 'package:flutter_screenutil/flutter_screenutil.dart';
  9. import 'package:flutter_vlc_player/flutter_vlc_player.dart';
  10. import '../../entity/db/local_patient_info.dart';
  11. import '../../funcs.dart';
  12. import '../../global.dart';
  13. ///视频预览页面
  14. @RoutePage(name: "videoPreviewRoute")
  15. class VideoPreviewPage extends StatefulWidget {
  16. final LocalPatientInfo info;
  17. final String path;
  18. final String area;
  19. final DateTime time;
  20. final String mobile;
  21. final String wifi;
  22. final String deviceModel;
  23. const VideoPreviewPage(
  24. {super.key,
  25. required this.info,
  26. required this.path,
  27. required this.area,
  28. required this.time,
  29. required this.mobile,
  30. required this.wifi,
  31. required this.deviceModel});
  32. @override
  33. State<VideoPreviewPage> createState() => _VideoPreviewPageState();
  34. }
  35. class _VideoPreviewPageState extends State<VideoPreviewPage> {
  36. VlcPlayerController? _playerController;
  37. bool _playVisible = false;
  38. Duration _totalDuration = const Duration();
  39. Duration _position = const Duration();
  40. bool _isSaving = false;
  41. bool _isSaved = false;
  42. @override
  43. void initState() {
  44. super.initState();
  45. _initPlayerController();
  46. }
  47. ///初始化视频控制器
  48. void _initPlayerController() async {
  49. _playerController = VlcPlayerController.file(
  50. File(widget.path),
  51. hwAcc: HwAcc.auto,
  52. autoInitialize: true,
  53. autoPlay: true,
  54. options: VlcPlayerOptions(),
  55. );
  56. //循环播放,setLooping方法无效
  57. _playerController!.addListener(() async {
  58. VlcPlayerValue value = _playerController!.value;
  59. if (value.isInitialized && mounted) {
  60. //更新播放进度
  61. setState(() {
  62. _totalDuration = value.duration;
  63. _position = value.position;
  64. });
  65. }
  66. if (value.playingState == PlayingState.ended) {
  67. await _playerController!.stop();
  68. _playerController!.play();
  69. }
  70. });
  71. }
  72. @override
  73. void dispose() async {
  74. super.dispose();
  75. _deleteCache();
  76. await _playerController?.stopRendererScanning();
  77. _playerController?.dispose();
  78. }
  79. @override
  80. Widget build(BuildContext context) {
  81. Widget videoView =
  82. (_playerController == null) ? const SizedBox() : _getVideoView();
  83. return PopScope(
  84. canPop: !_isSaving,
  85. child: Scaffold(
  86. body: SafeArea(
  87. child: Container(
  88. color: Colors.black,
  89. child: Stack(
  90. children: [
  91. videoView,
  92. Align(
  93. alignment: Alignment.topLeft,
  94. child: PreviewTitle(
  95. title: getS().previewVideo,
  96. ),
  97. ),
  98. PreviewOperationView(
  99. onSave: _onSave,
  100. onCancel: _onCancel,
  101. ),
  102. ],
  103. ),
  104. ),
  105. ),
  106. ));
  107. }
  108. Widget _getVideoView() {
  109. return Stack(
  110. children: [
  111. GestureDetector(
  112. behavior: HitTestBehavior.opaque,
  113. onTap: _onTap,
  114. child: Stack(
  115. children: [
  116. _getVideoPlayer(),
  117. _getPlayVisible(),
  118. ],
  119. ),
  120. ),
  121. _getSeekBar()
  122. ],
  123. );
  124. }
  125. Widget _getVideoPlayer() {
  126. return Container(
  127. color: Colors.black,
  128. alignment: Alignment.center,
  129. child: SizedBox.expand(
  130. child: AspectRatio(
  131. aspectRatio: _playerController!.value.aspectRatio,
  132. child: VlcPlayer(
  133. controller: _playerController!,
  134. aspectRatio: 16 / 9,
  135. placeholder: const Center(child: CircularProgressIndicator()),
  136. ),
  137. ),
  138. ),
  139. );
  140. }
  141. Widget _getPlayVisible() {
  142. return Align(
  143. alignment: Alignment.center,
  144. child: Visibility(
  145. visible: _playVisible,
  146. child: Icon(
  147. size: 100.r,
  148. Icons.play_circle_outline,
  149. color: Colors.white60,
  150. )),
  151. );
  152. }
  153. Widget _getSeekBar() {
  154. return Visibility(
  155. visible: _totalDuration != Duration.zero,
  156. child: Align(
  157. alignment: Alignment.bottomCenter,
  158. child: Padding(
  159. padding: EdgeInsets.only(bottom: 20.h, left: 20.w, right: 20.w),
  160. child: ProgressBar(
  161. progress: _position,
  162. total: _totalDuration,
  163. baseBarColor: Colors.white,
  164. thumbColor: Colors.white,
  165. timeLabelTextStyle: const TextStyle(color: Colors.white),
  166. timeLabelLocation: TimeLabelLocation.sides,
  167. onSeek: (duration) => _playerController!.seekTo(duration),
  168. ),
  169. ),
  170. ));
  171. }
  172. void _onTap() async {
  173. if (_playerController == null) {
  174. return;
  175. }
  176. bool? bo = await _playerController!.isPlaying();
  177. if (bo != null && bo) {
  178. await _playerController!.pause();
  179. setState(() {
  180. _playVisible = true;
  181. });
  182. } else {
  183. await _playerController!.play();
  184. setState(() {
  185. _playVisible = false;
  186. });
  187. }
  188. }
  189. void _onSave() async {
  190. logd("保存");
  191. setState(() {
  192. _isSaving = true;
  193. });
  194. var cancelFunc = BotToast.showLoading(clickClose: false, crossPage: false);
  195. try {
  196. await videoChannel.invokeMethod("saveRecord", [widget.path, widget.path]);
  197. _isSaved = true;
  198. cancelFunc();
  199. showToast(text: getS().saveSuccess);
  200. setState(() {
  201. _isSaving = false;
  202. });
  203. if (mounted) {
  204. Navigator.pop(context);
  205. }
  206. } catch (e) {
  207. setState(() {
  208. _isSaving = false;
  209. });
  210. cancelFunc();
  211. showToast(text: getS().saveFailed);
  212. loge("保存视频异常", error: e);
  213. }
  214. }
  215. void _onCancel() {
  216. logd("取消");
  217. Navigator.pop(context);
  218. }
  219. void _deleteCache() {
  220. if (widget.path.isEmpty || _isSaved) {
  221. return;
  222. }
  223. try {
  224. fileChannel.invokeMethod("deleteFile", widget.path);
  225. } catch (e) {
  226. loge("照片预览删除文件异常", error: e);
  227. }
  228. }
  229. }