Reason Pun

视频录制页面增加了计时器

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
......