From 0763fa5d50147d6564f3bb96e51c13d7ae8c632e Mon Sep 17 00:00:00 2001 From: otsmr Date: Tue, 22 Apr 2025 21:06:59 +0200 Subject: [PATCH] capture of video does work #25 --- lib/src/views/camera/camera_preview_view.dart | 154 +++++++++++++----- .../views/camera/share_image_editor_view.dart | 13 +- 2 files changed, 123 insertions(+), 44 deletions(-) diff --git a/lib/src/views/camera/camera_preview_view.dart b/lib/src/views/camera/camera_preview_view.dart index 48c9f53..cd1aabf 100644 --- a/lib/src/views/camera/camera_preview_view.dart +++ b/lib/src/views/camera/camera_preview_view.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; @@ -41,10 +42,13 @@ class _CameraPreviewViewState extends State { bool useHighQuality = false; bool isVideoRecording = false; bool hasAudioPermission = true; + DateTime? videoRecordingStarted; + Timer? videoRecordingTimer; + DateTime currentTime = DateTime.now(); final GlobalKey keyTriggerButton = GlobalKey(); final GlobalKey navigatorKey = GlobalKey(); - late CameraController controller; + CameraController? controller; ScreenshotController screenshotController = ScreenshotController(); @override @@ -59,7 +63,9 @@ class _CameraPreviewViewState extends State { return; } if (sharePreviewIsShown) return; - if (controller.value.isInitialized) takePicture(); + if (controller != null && controller!.value.isInitialized) { + takePicture(); + } }, ); initAsync(); @@ -79,8 +85,9 @@ class _CameraPreviewViewState extends State { void dispose() { FlutterVolumeController.removeListener(); if (cameraId < gCameras.length) { - controller.dispose(); + controller?.dispose(); } + videoRecordingTimer?.cancel(); super.dispose(); } @@ -92,11 +99,13 @@ class _CameraPreviewViewState extends State { openAppSettings(); } else { hasAudioPermission = await Permission.microphone.isGranted; - setState(() {}); + if (hasAudioPermission) { + selectCamera(cameraId); + } } } - void selectCamera(int sCameraId, {bool init = false}) { + Future selectCamera(int sCameraId, {bool init = false}) async { if (sCameraId >= gCameras.length) return; if (init) { for (; sCameraId < gCameras.length; sCameraId++) { @@ -111,17 +120,17 @@ class _CameraPreviewViewState extends State { controller = CameraController( gCameras[sCameraId], ResolutionPreset.high, - enableAudio: false, + enableAudio: await Permission.microphone.isGranted, ); - controller.initialize().then((_) async { + controller?.initialize().then((_) async { if (!mounted) { return; } - await controller.lockCaptureOrientation(DeviceOrientation.portraitUp); - controller.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off); + await controller?.lockCaptureOrientation(DeviceOrientation.portraitUp); + controller?.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off); - isZoomAble = await controller.getMinZoomLevel() != - await controller.getMaxZoomLevel(); + isZoomAble = await controller?.getMinZoomLevel() != + await controller?.getMaxZoomLevel(); setState(() { cameraLoaded = true; }); @@ -143,9 +152,9 @@ class _CameraPreviewViewState extends State { } Future updateScaleFactor(double newScale) async { - if (scaleFactor == newScale) return; - var minFactor = await controller.getMinZoomLevel(); - var maxFactor = await controller.getMaxZoomLevel(); + if (scaleFactor == newScale || controller == null) return; + var minFactor = await controller!.getMinZoomLevel(); + var maxFactor = await controller!.getMaxZoomLevel(); if (newScale < minFactor) { newScale = minFactor; } @@ -153,7 +162,7 @@ class _CameraPreviewViewState extends State { newScale = maxFactor; } - await controller.setZoomLevel(newScale); + await controller?.setZoomLevel(newScale); setState(() { scaleFactor = newScale; }); @@ -181,7 +190,7 @@ class _CameraPreviewViewState extends State { } Future takePicture() async { - if (sharePreviewIsShown) return; + if (sharePreviewIsShown || isVideoRecording) return; late Future imageBytes; setState(() { @@ -190,12 +199,13 @@ class _CameraPreviewViewState extends State { if (useHighQuality && !isFront) { if (Platform.isIOS) { - await controller.pausePreview(); + await controller?.pausePreview(); if (!context.mounted) return; } try { // Take the picture - final XFile picture = await controller.takePicture(); + final XFile? picture = await controller?.takePicture(); + if (picture == null) return; imageBytes = loadAndDeletePictureFromFile(picture); } catch (e) { _showCameraException(e); @@ -208,17 +218,17 @@ class _CameraPreviewViewState extends State { showSelfieFlash = true; }); } else { - controller.setFlashMode(FlashMode.torch); + controller?.setFlashMode(FlashMode.torch); } await Future.delayed(Duration(milliseconds: 1000)); } - await controller.pausePreview(); + await controller?.pausePreview(); if (!context.mounted) return; - controller.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off); - - imageBytes = screenshotController.capture(pixelRatio: 1); + controller?.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off); + imageBytes = screenshotController.capture( + pixelRatio: MediaQuery.of(context).devicePixelRatio); } if (await pushMediaEditor(imageBytes, null)) { @@ -262,10 +272,9 @@ class _CameraPreviewViewState extends State { } bool get isFront => - controller.description.lensDirection == CameraLensDirection.front; + controller?.description.lensDirection == CameraLensDirection.front; Future onPanUpdate(details) async { - print(details); if (isFront) { return; } @@ -275,7 +284,13 @@ class _CameraPreviewViewState extends State { if (diff > baseDiff) diff = baseDiff; if (diff < -baseDiff) diff = -baseDiff; - var tmp = (diff / baseDiff * (14 * 2)).toInt() / 4; + var tmp = 0.0; + if (Platform.isAndroid) { + tmp = (diff / baseDiff * (7 * 2)).toInt() / 2; + } else { + tmp = (diff / baseDiff * (14 * 2)).toInt() / 4; + } + tmp = baseScaleFactor + tmp; if (tmp < 1) tmp = 1; updateScaleFactor(tmp); @@ -302,11 +317,24 @@ class _CameraPreviewViewState extends State { } Future startVideoRecording() async { - if (controller.value.isRecordingVideo) return; + if (controller != null && controller!.value.isRecordingVideo) return; try { - await controller.startVideoRecording(); + await controller?.startVideoRecording(); + videoRecordingTimer = Timer.periodic(Duration(milliseconds: 10), (timer) { + setState(() { + currentTime = DateTime.now(); + }); + + if (videoRecordingStarted != null && + currentTime.difference(videoRecordingStarted!).inSeconds >= 10) { + timer.cancel(); + videoRecordingTimer = null; + stopVideoRecording(); + } + }); setState(() { + videoRecordingStarted = DateTime.now(); isVideoRecording = true; }); } on CameraException catch (e) { @@ -316,17 +344,22 @@ class _CameraPreviewViewState extends State { } Future stopVideoRecording() async { - if (!controller.value.isRecordingVideo) { + if (videoRecordingTimer != null) { + videoRecordingTimer?.cancel(); + videoRecordingTimer = null; + } + if (controller == null || !controller!.value.isRecordingVideo) { return null; } try { setState(() { + videoRecordingStarted = null; isVideoRecording = false; sharePreviewIsShown = true; }); - XFile? videoPath = await controller.stopVideoRecording(); - await controller.pausePreview(); + XFile? videoPath = await controller?.stopVideoRecording(); + await controller?.pausePreview(); if (await pushMediaEditor(null, videoPath)) { return; } @@ -354,7 +387,7 @@ class _CameraPreviewViewState extends State { @override Widget build(BuildContext context) { - if (cameraId >= gCameras.length) { + if (cameraId >= gCameras.length || controller == null) { return Center( child: Text("No camera found."), ); @@ -379,7 +412,6 @@ class _CameraPreviewViewState extends State { basePanY = details.localPosition.dy; baseScaleFactor = scaleFactor; }); - print("onLongPressDown"); // Get the position of the pointer RenderBox renderBox = keyTriggerButton.currentContext?.findRenderObject() as RenderBox; @@ -405,7 +437,7 @@ class _CameraPreviewViewState extends State { children: [ if (!galleryLoadedImageIsShown) CameraPreviewWidget( - controller: controller, + controller: controller!, screenshotController: screenshotController, isFront: isFront, ), @@ -452,10 +484,10 @@ class _CameraPreviewViewState extends State { : Colors.white.withAlpha(160), onPressed: () async { if (isFlashOn) { - controller.setFlashMode(FlashMode.off); + controller?.setFlashMode(FlashMode.off); isFlashOn = false; } else { - controller.setFlashMode(FlashMode.always); + controller?.setFlashMode(FlashMode.always); isFlashOn = true; } setState(() {}); @@ -500,7 +532,7 @@ class _CameraPreviewViewState extends State { alignment: Alignment.bottomCenter, child: Column( children: [ - if (controller.value.isInitialized && + if (controller!.value.isInitialized && isZoomAble && !isFront && !isVideoRecording) @@ -510,7 +542,7 @@ class _CameraPreviewViewState extends State { key: widget.key, scaleFactor: scaleFactor, updateScaleFactor: updateScaleFactor, - controller: controller, + controller: controller!, ), ), const SizedBox(height: 30), @@ -570,6 +602,52 @@ class _CameraPreviewViewState extends State { ], ), ), + if (videoRecordingStarted != null) + Positioned( + top: 50, + left: 0, + right: 0, + child: Center( + child: SizedBox( + width: 50, + height: 50, + child: Stack( + children: [ + Center( + child: CircularProgressIndicator( + value: + (currentTime.difference(videoRecordingStarted!)) + .inMilliseconds / + (10 * 1000), + strokeWidth: 4, + valueColor: + AlwaysStoppedAnimation(Colors.red), + backgroundColor: Colors.grey[300], + ), + ), + Center( + child: Text( + currentTime + .difference(videoRecordingStarted!) + .inSeconds + .toString(), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 17, + shadows: [ + Shadow( + color: const Color.fromARGB(122, 0, 0, 0), + blurRadius: 5.0, + ) + ], + ), + ), + ) + ], + ), + ), + ), + ), if (!sharePreviewIsShown && widget.sendTo != null) Positioned( left: 5, diff --git a/lib/src/views/camera/share_image_editor_view.dart b/lib/src/views/camera/share_image_editor_view.dart index 852514a..b3d3a6f 100644 --- a/lib/src/views/camera/share_image_editor_view.dart +++ b/lib/src/views/camera/share_image_editor_view.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:logging/logging.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/views/camera/components/save_to_gallery.dart'; import 'package:twonly/src/views/camera/image_editor/action_button.dart'; @@ -59,16 +60,12 @@ class _ShareImageEditorView extends State { } else if (widget.videFilePath != null) { videoController = VideoPlayerController.file(File(widget.videFilePath!.path)); - videoController?.addListener(() { - setState(() {}); - }); videoController?.setLooping(true); videoController?.initialize().then((_) { videoController!.play(); - setState(() {}); }).catchError((Object error) { - print(error); + Logger("ui.share_image_editor").shout(error); }); videoController?.play(); print(widget.videFilePath!.path); @@ -260,6 +257,7 @@ class _ShareImageEditorView extends State { ), ); if (wasSend != null && wasSend && context.mounted) { + // ignore: use_build_context_synchronously Navigator.pop(context, true); } } @@ -321,7 +319,10 @@ class _ShareImageEditorView extends State { _isRealTwonly, maxShowTime, ); - Navigator.pop(context, true); + if (context.mounted) { + // ignore: use_build_context_synchronously + Navigator.pop(context, true); + } } @override