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