Showing
3 changed files
with
107 additions
and
67 deletions
1 | +import 'dart:async'; | ||
1 | import 'dart:io'; | 2 | import 'dart:io'; |
2 | 3 | ||
3 | import 'package:camera/camera.dart'; | 4 | import 'package:camera/camera.dart'; |
... | @@ -6,6 +7,7 @@ import 'package:flutter/services.dart'; | ... | @@ -6,6 +7,7 @@ import 'package:flutter/services.dart'; |
6 | import 'package:one_poem/util/toast_utils.dart'; | 7 | import 'package:one_poem/util/toast_utils.dart'; |
7 | import 'package:one_poem/widgets/my_app_bar.dart'; | 8 | import 'package:one_poem/widgets/my_app_bar.dart'; |
8 | import 'package:path_provider/path_provider.dart'; | 9 | import 'package:path_provider/path_provider.dart'; |
10 | +import 'package:pausable_timer/pausable_timer.dart'; | ||
9 | import 'package:video_player/video_player.dart'; | 11 | import 'package:video_player/video_player.dart'; |
10 | import 'package:one_poem/extension/int_extension.dart'; | 12 | import 'package:one_poem/extension/int_extension.dart'; |
11 | 13 | ||
... | @@ -21,8 +23,7 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> | ... | @@ -21,8 +23,7 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> |
21 | CameraController? controller; | 23 | CameraController? controller; |
22 | VideoPlayerController? videoController; | 24 | VideoPlayerController? videoController; |
23 | 25 | ||
24 | - File? _imageFile; | 26 | + File? _videoFile; // 保存的视频文件位置,用于上传视频的时候用! |
25 | - File? _videoFile; | ||
26 | 27 | ||
27 | final bool _isVideoCameraSelected = true; | 28 | final bool _isVideoCameraSelected = true; |
28 | bool _isCameraInitialized = false; | 29 | bool _isCameraInitialized = false; |
... | @@ -36,6 +37,15 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> | ... | @@ -36,6 +37,15 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> |
36 | ResolutionPreset currentResolutionPreset = ResolutionPreset.high; | 37 | ResolutionPreset currentResolutionPreset = ResolutionPreset.high; |
37 | 38 | ||
38 | List<CameraDescription>? cameras = []; | 39 | List<CameraDescription>? cameras = []; |
40 | + | ||
41 | + ///声明变量 | ||
42 | + late final PausableTimer _timer; | ||
43 | + | ||
44 | + ///记录当前的时间 | ||
45 | + int currentTimer = 0; | ||
46 | + | ||
47 | + int duration = 60 * 1000; | ||
48 | + | ||
39 | @override | 49 | @override |
40 | void initState() { | 50 | void initState() { |
41 | // Hide the status bar in Android | 51 | // Hide the status bar in Android |
... | @@ -49,6 +59,20 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> | ... | @@ -49,6 +59,20 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> |
49 | Toast.show("摄像头出错啦~"); | 59 | Toast.show("摄像头出错啦~"); |
50 | } | 60 | } |
51 | }); | 61 | }); |
62 | + | ||
63 | + _timer = PausableTimer( | ||
64 | + const Duration(milliseconds: 100), | ||
65 | + () { | ||
66 | + currentTimer += 100; | ||
67 | + _timer | ||
68 | + ..reset() | ||
69 | + ..start(); | ||
70 | + if (currentTimer >= duration) { | ||
71 | + stopRecordVideo(); | ||
72 | + } | ||
73 | + setState(() {}); | ||
74 | + }, | ||
75 | + ); | ||
52 | super.initState(); | 76 | super.initState(); |
53 | } | 77 | } |
54 | 78 | ||
... | @@ -76,22 +100,6 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> | ... | @@ -76,22 +100,6 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> |
76 | fileNames.add({0: int.parse(name), 1: file.path.split('/').last}); | 100 | fileNames.add({0: int.parse(name), 1: file.path.split('/').last}); |
77 | } | 101 | } |
78 | } | 102 | } |
79 | - | ||
80 | - if (fileNames.isNotEmpty) { | ||
81 | - final recentFile = | ||
82 | - fileNames.reduce((curr, next) => curr[0] > next[0] ? curr : next); | ||
83 | - String recentFileName = recentFile[1]; | ||
84 | - if (recentFileName.contains('.mp4')) { | ||
85 | - _videoFile = File('${directory.path}/$recentFileName'); | ||
86 | - _imageFile = null; | ||
87 | - _startVideoPlayer(); | ||
88 | - } else { | ||
89 | - _imageFile = File('${directory.path}/$recentFileName'); | ||
90 | - _videoFile = null; | ||
91 | - } | ||
92 | - | ||
93 | - setState(() {}); | ||
94 | - } | ||
95 | } | 103 | } |
96 | 104 | ||
97 | Future<XFile?> takePicture() async { | 105 | Future<XFile?> takePicture() async { |
... | @@ -110,19 +118,6 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> | ... | @@ -110,19 +118,6 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> |
110 | } | 118 | } |
111 | } | 119 | } |
112 | 120 | ||
113 | - Future<void> _startVideoPlayer() async { | ||
114 | - if (_videoFile != null) { | ||
115 | - videoController = VideoPlayerController.file(_videoFile!); | ||
116 | - await videoController!.initialize().then((_) { | ||
117 | - // Ensure the first frame is shown after the video is initialized, | ||
118 | - // even before the play button has been pressed. | ||
119 | - setState(() {}); | ||
120 | - }); | ||
121 | - await videoController!.setLooping(true); | ||
122 | - await videoController!.play(); | ||
123 | - } | ||
124 | - } | ||
125 | - | ||
126 | Future<void> startVideoRecording() async { | 121 | Future<void> startVideoRecording() async { |
127 | final CameraController? cameraController = controller; | 122 | final CameraController? cameraController = controller; |
128 | 123 | ||
... | @@ -239,9 +234,34 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> | ... | @@ -239,9 +234,34 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> |
239 | void dispose() { | 234 | void dispose() { |
240 | controller?.dispose(); | 235 | controller?.dispose(); |
241 | videoController?.dispose(); | 236 | videoController?.dispose(); |
237 | + | ||
238 | + ///取消计时器 | ||
239 | + _timer.cancel(); | ||
242 | super.dispose(); | 240 | super.dispose(); |
243 | } | 241 | } |
244 | 242 | ||
243 | + Future<void> stopRecordVideo() async { | ||
244 | + try { | ||
245 | + XFile? rawVideo = await stopVideoRecording(); | ||
246 | + File videoFile = File(rawVideo!.path); | ||
247 | + | ||
248 | + int currentUnix = DateTime.now().millisecondsSinceEpoch; | ||
249 | + | ||
250 | + final directory = await getApplicationDocumentsDirectory(); | ||
251 | + | ||
252 | + String fileFormat = videoFile.path.split('.').last; | ||
253 | + | ||
254 | + _videoFile = await videoFile.copy( | ||
255 | + '${directory.path}/$currentUnix.$fileFormat', | ||
256 | + ); | ||
257 | + | ||
258 | + // TODO why pause!直接使用cancel()会出现问题,暂时这么解决 | ||
259 | + _timer.pause(); | ||
260 | + } catch (e) { | ||
261 | + // print(e); | ||
262 | + } | ||
263 | + } | ||
264 | + | ||
245 | @override | 265 | @override |
246 | Widget build(BuildContext context) { | 266 | Widget build(BuildContext context) { |
247 | return SafeArea( | 267 | return SafeArea( |
... | @@ -266,11 +286,11 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> | ... | @@ -266,11 +286,11 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> |
266 | ), | 286 | ), |
267 | ), | 287 | ), |
268 | Padding( | 288 | Padding( |
269 | - padding: const EdgeInsets.fromLTRB( | 289 | + padding: EdgeInsets.fromLTRB( |
270 | - 16.0, | 290 | + 16.px, |
271 | - 8.0, | 291 | + 8.px, |
272 | - 16.0, | 292 | + 16.px, |
273 | - 8.0, | 293 | + 8.px, |
274 | ), | 294 | ), |
275 | child: Column( | 295 | child: Column( |
276 | crossAxisAlignment: CrossAxisAlignment.end, | 296 | crossAxisAlignment: CrossAxisAlignment.end, |
... | @@ -281,14 +301,17 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> | ... | @@ -281,14 +301,17 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> |
281 | ), | 301 | ), |
282 | Row( | 302 | Row( |
283 | mainAxisAlignment: MainAxisAlignment.spaceBetween, | 303 | mainAxisAlignment: MainAxisAlignment.spaceBetween, |
304 | + crossAxisAlignment: CrossAxisAlignment.end, | ||
284 | children: [ | 305 | children: [ |
285 | InkWell( | 306 | InkWell( |
286 | onTap: _isRecordingInProgress | 307 | onTap: _isRecordingInProgress |
287 | ? () async { | 308 | ? () async { |
288 | if (controller!.value.isRecordingPaused) { | 309 | if (controller!.value.isRecordingPaused) { |
289 | await resumeVideoRecording(); | 310 | await resumeVideoRecording(); |
311 | + _timer.start(); | ||
290 | } else { | 312 | } else { |
291 | await pauseVideoRecording(); | 313 | await pauseVideoRecording(); |
314 | + _timer.pause(); | ||
292 | } | 315 | } |
293 | } | 316 | } |
294 | : () { | 317 | : () { |
... | @@ -305,29 +328,29 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> | ... | @@ -305,29 +328,29 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> |
305 | child: Stack( | 328 | child: Stack( |
306 | alignment: Alignment.center, | 329 | alignment: Alignment.center, |
307 | children: [ | 330 | children: [ |
308 | - const Icon( | 331 | + Icon( |
309 | Icons.circle, | 332 | Icons.circle, |
310 | color: Colors.black38, | 333 | color: Colors.black38, |
311 | - size: 60, | 334 | + size: 60.px, |
312 | ), | 335 | ), |
313 | _isRecordingInProgress | 336 | _isRecordingInProgress |
314 | ? controller!.value.isRecordingPaused | 337 | ? controller!.value.isRecordingPaused |
315 | - ? const Icon( | 338 | + ? Icon( |
316 | Icons.play_arrow, | 339 | Icons.play_arrow, |
317 | color: Colors.white, | 340 | color: Colors.white, |
318 | - size: 30, | 341 | + size: 30.px, |
319 | ) | 342 | ) |
320 | - : const Icon( | 343 | + : Icon( |
321 | Icons.pause, | 344 | Icons.pause, |
322 | color: Colors.white, | 345 | color: Colors.white, |
323 | - size: 30, | 346 | + size: 30.px, |
324 | ) | 347 | ) |
325 | : Icon( | 348 | : Icon( |
326 | _isRearCameraSelected | 349 | _isRearCameraSelected |
327 | ? Icons.camera_front | 350 | ? Icons.camera_front |
328 | : Icons.camera_rear, | 351 | : Icons.camera_rear, |
329 | color: Colors.white, | 352 | color: Colors.white, |
330 | - size: 30, | 353 | + size: 30.px, |
331 | ), | 354 | ), |
332 | ], | 355 | ], |
333 | ), | 356 | ), |
... | @@ -335,24 +358,12 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> | ... | @@ -335,24 +358,12 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> |
335 | InkWell( | 358 | InkWell( |
336 | onTap: () async { | 359 | onTap: () async { |
337 | if (_isRecordingInProgress) { | 360 | if (_isRecordingInProgress) { |
338 | - XFile? rawVideo = await stopVideoRecording(); | 361 | + stopRecordVideo(); |
339 | - File videoFile = File(rawVideo!.path); | ||
340 | - | ||
341 | - int currentUnix = | ||
342 | - DateTime.now().millisecondsSinceEpoch; | ||
343 | - | ||
344 | - final directory = | ||
345 | - await getApplicationDocumentsDirectory(); | ||
346 | - | ||
347 | - String fileFormat = | ||
348 | - videoFile.path.split('.').last; | ||
349 | - | ||
350 | - _videoFile = await videoFile.copy( | ||
351 | - '${directory.path}/$currentUnix.$fileFormat', | ||
352 | - ); | ||
353 | - | ||
354 | - _startVideoPlayer(); | ||
355 | } else { | 362 | } else { |
363 | + currentTimer = 0; | ||
364 | + _timer | ||
365 | + ..reset() | ||
366 | + ..start(); | ||
356 | await startVideoRecording(); | 367 | await startVideoRecording(); |
357 | } | 368 | } |
358 | }, | 369 | }, |
... | @@ -364,21 +375,40 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> | ... | @@ -364,21 +375,40 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> |
364 | color: _isVideoCameraSelected | 375 | color: _isVideoCameraSelected |
365 | ? Colors.white | 376 | ? Colors.white |
366 | : Colors.white38, | 377 | : Colors.white38, |
367 | - size: 80, | 378 | + size: 80.px, |
368 | ), | 379 | ), |
380 | + _isRecordingInProgress | ||
381 | + ? SizedBox( | ||
382 | + width: 60.px, | ||
383 | + height: 60.px, | ||
384 | + child: CircularProgressIndicator( | ||
385 | + strokeWidth: 5.px, | ||
386 | + value: currentTimer / duration, | ||
387 | + ), | ||
388 | + ) | ||
389 | + : Container(), | ||
369 | Icon( | 390 | Icon( |
370 | Icons.circle, | 391 | Icons.circle, |
371 | color: _isVideoCameraSelected | 392 | color: _isVideoCameraSelected |
372 | ? Colors.red | 393 | ? Colors.red |
373 | : Colors.white, | 394 | : Colors.white, |
374 | - size: 65, | 395 | + size: 65.px, |
375 | ), | 396 | ), |
397 | + _isRecordingInProgress | ||
398 | + ? Container() | ||
399 | + : Text( | ||
400 | + "60s", | ||
401 | + style: TextStyle( | ||
402 | + fontSize: 12.px, | ||
403 | + color: Colors.white, | ||
404 | + ), | ||
405 | + ), | ||
376 | _isVideoCameraSelected && | 406 | _isVideoCameraSelected && |
377 | _isRecordingInProgress | 407 | _isRecordingInProgress |
378 | - ? const Icon( | 408 | + ? Icon( |
379 | Icons.stop_rounded, | 409 | Icons.stop_rounded, |
380 | color: Colors.white, | 410 | color: Colors.white, |
381 | - size: 32, | 411 | + size: 32.px, |
382 | ) | 412 | ) |
383 | : Container(), | 413 | : Container(), |
384 | ], | 414 | ], |
... | @@ -388,16 +418,16 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> | ... | @@ -388,16 +418,16 @@ class _PoemRecordVideoPageState extends State<PoemRecordVideoPage> |
388 | onTap: () {}, | 418 | onTap: () {}, |
389 | child: Stack( | 419 | child: Stack( |
390 | alignment: Alignment.center, | 420 | alignment: Alignment.center, |
391 | - children: const [ | 421 | + children: [ |
392 | Icon( | 422 | Icon( |
393 | Icons.circle, | 423 | Icons.circle, |
394 | color: Colors.black38, | 424 | color: Colors.black38, |
395 | - size: 60, | 425 | + size: 60.px, |
396 | ), | 426 | ), |
397 | Icon( | 427 | Icon( |
398 | Icons.photo_album, | 428 | Icons.photo_album, |
399 | color: Colors.white, | 429 | color: Colors.white, |
400 | - size: 30, | 430 | + size: 30.px, |
401 | ), | 431 | ), |
402 | ], | 432 | ], |
403 | ), | 433 | ), | ... | ... |
... | @@ -701,6 +701,13 @@ packages: | ... | @@ -701,6 +701,13 @@ packages: |
701 | url: "https://pub.dartlang.org" | 701 | url: "https://pub.dartlang.org" |
702 | source: hosted | 702 | source: hosted |
703 | version: "2.0.5" | 703 | version: "2.0.5" |
704 | + pausable_timer: | ||
705 | + dependency: "direct main" | ||
706 | + description: | ||
707 | + name: pausable_timer | ||
708 | + url: "https://pub.dartlang.org" | ||
709 | + source: hosted | ||
710 | + version: "1.0.0+3" | ||
704 | pedantic: | 711 | pedantic: |
705 | dependency: transitive | 712 | dependency: transitive |
706 | description: | 713 | description: | ... | ... |
... | @@ -99,6 +99,9 @@ dependencies: | ... | @@ -99,6 +99,9 @@ dependencies: |
99 | camera: ^0.9.4+5 | 99 | camera: ^0.9.4+5 |
100 | path_provider: ^2.0.8 | 100 | path_provider: ^2.0.8 |
101 | 101 | ||
102 | + # A Dart timer that can be paused, resumed and reset. | ||
103 | + pausable_timer: ^1.0.0+3 | ||
104 | + | ||
102 | dependency_overrides: | 105 | dependency_overrides: |
103 | decimal: 1.5.0 | 106 | decimal: 1.5.0 |
104 | 107 | ... | ... |
-
Please register or login to post a comment