capture of video does work #25

This commit is contained in:
otsmr 2025-04-22 21:06:59 +02:00
parent 5e90af79d8
commit 0763fa5d50
2 changed files with 123 additions and 44 deletions

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -41,10 +42,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
bool useHighQuality = false; bool useHighQuality = false;
bool isVideoRecording = false; bool isVideoRecording = false;
bool hasAudioPermission = true; bool hasAudioPermission = true;
DateTime? videoRecordingStarted;
Timer? videoRecordingTimer;
DateTime currentTime = DateTime.now();
final GlobalKey keyTriggerButton = GlobalKey(); final GlobalKey keyTriggerButton = GlobalKey();
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
late CameraController controller; CameraController? controller;
ScreenshotController screenshotController = ScreenshotController(); ScreenshotController screenshotController = ScreenshotController();
@override @override
@ -59,7 +63,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
return; return;
} }
if (sharePreviewIsShown) return; if (sharePreviewIsShown) return;
if (controller.value.isInitialized) takePicture(); if (controller != null && controller!.value.isInitialized) {
takePicture();
}
}, },
); );
initAsync(); initAsync();
@ -79,8 +85,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
void dispose() { void dispose() {
FlutterVolumeController.removeListener(); FlutterVolumeController.removeListener();
if (cameraId < gCameras.length) { if (cameraId < gCameras.length) {
controller.dispose(); controller?.dispose();
} }
videoRecordingTimer?.cancel();
super.dispose(); super.dispose();
} }
@ -92,11 +99,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
openAppSettings(); openAppSettings();
} else { } else {
hasAudioPermission = await Permission.microphone.isGranted; 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 (sCameraId >= gCameras.length) return;
if (init) { if (init) {
for (; sCameraId < gCameras.length; sCameraId++) { for (; sCameraId < gCameras.length; sCameraId++) {
@ -111,17 +120,17 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
controller = CameraController( controller = CameraController(
gCameras[sCameraId], gCameras[sCameraId],
ResolutionPreset.high, ResolutionPreset.high,
enableAudio: false, enableAudio: await Permission.microphone.isGranted,
); );
controller.initialize().then((_) async { controller?.initialize().then((_) async {
if (!mounted) { if (!mounted) {
return; return;
} }
await controller.lockCaptureOrientation(DeviceOrientation.portraitUp); await controller?.lockCaptureOrientation(DeviceOrientation.portraitUp);
controller.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off); controller?.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off);
isZoomAble = await controller.getMinZoomLevel() != isZoomAble = await controller?.getMinZoomLevel() !=
await controller.getMaxZoomLevel(); await controller?.getMaxZoomLevel();
setState(() { setState(() {
cameraLoaded = true; cameraLoaded = true;
}); });
@ -143,9 +152,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
} }
Future<void> updateScaleFactor(double newScale) async { Future<void> updateScaleFactor(double newScale) async {
if (scaleFactor == newScale) return; if (scaleFactor == newScale || controller == null) return;
var minFactor = await controller.getMinZoomLevel(); var minFactor = await controller!.getMinZoomLevel();
var maxFactor = await controller.getMaxZoomLevel(); var maxFactor = await controller!.getMaxZoomLevel();
if (newScale < minFactor) { if (newScale < minFactor) {
newScale = minFactor; newScale = minFactor;
} }
@ -153,7 +162,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
newScale = maxFactor; newScale = maxFactor;
} }
await controller.setZoomLevel(newScale); await controller?.setZoomLevel(newScale);
setState(() { setState(() {
scaleFactor = newScale; scaleFactor = newScale;
}); });
@ -181,7 +190,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
} }
Future takePicture() async { Future takePicture() async {
if (sharePreviewIsShown) return; if (sharePreviewIsShown || isVideoRecording) return;
late Future<Uint8List?> imageBytes; late Future<Uint8List?> imageBytes;
setState(() { setState(() {
@ -190,12 +199,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
if (useHighQuality && !isFront) { if (useHighQuality && !isFront) {
if (Platform.isIOS) { if (Platform.isIOS) {
await controller.pausePreview(); await controller?.pausePreview();
if (!context.mounted) return; if (!context.mounted) return;
} }
try { try {
// Take the picture // Take the picture
final XFile picture = await controller.takePicture(); final XFile? picture = await controller?.takePicture();
if (picture == null) return;
imageBytes = loadAndDeletePictureFromFile(picture); imageBytes = loadAndDeletePictureFromFile(picture);
} catch (e) { } catch (e) {
_showCameraException(e); _showCameraException(e);
@ -208,17 +218,17 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
showSelfieFlash = true; showSelfieFlash = true;
}); });
} else { } else {
controller.setFlashMode(FlashMode.torch); controller?.setFlashMode(FlashMode.torch);
} }
await Future.delayed(Duration(milliseconds: 1000)); await Future.delayed(Duration(milliseconds: 1000));
} }
await controller.pausePreview(); await controller?.pausePreview();
if (!context.mounted) return; if (!context.mounted) return;
controller.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off); controller?.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off);
imageBytes = screenshotController.capture(
imageBytes = screenshotController.capture(pixelRatio: 1); pixelRatio: MediaQuery.of(context).devicePixelRatio);
} }
if (await pushMediaEditor(imageBytes, null)) { if (await pushMediaEditor(imageBytes, null)) {
@ -262,10 +272,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
} }
bool get isFront => bool get isFront =>
controller.description.lensDirection == CameraLensDirection.front; controller?.description.lensDirection == CameraLensDirection.front;
Future onPanUpdate(details) async { Future onPanUpdate(details) async {
print(details);
if (isFront) { if (isFront) {
return; return;
} }
@ -275,7 +284,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
if (diff > baseDiff) diff = baseDiff; if (diff > baseDiff) diff = baseDiff;
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; tmp = baseScaleFactor + tmp;
if (tmp < 1) tmp = 1; if (tmp < 1) tmp = 1;
updateScaleFactor(tmp); updateScaleFactor(tmp);
@ -302,11 +317,24 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
} }
Future startVideoRecording() async { Future startVideoRecording() async {
if (controller.value.isRecordingVideo) return; if (controller != null && controller!.value.isRecordingVideo) return;
try { 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(() { setState(() {
videoRecordingStarted = DateTime.now();
isVideoRecording = true; isVideoRecording = true;
}); });
} on CameraException catch (e) { } on CameraException catch (e) {
@ -316,17 +344,22 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
} }
Future stopVideoRecording() async { Future stopVideoRecording() async {
if (!controller.value.isRecordingVideo) { if (videoRecordingTimer != null) {
videoRecordingTimer?.cancel();
videoRecordingTimer = null;
}
if (controller == null || !controller!.value.isRecordingVideo) {
return null; return null;
} }
try { try {
setState(() { setState(() {
videoRecordingStarted = null;
isVideoRecording = false; isVideoRecording = false;
sharePreviewIsShown = true; sharePreviewIsShown = true;
}); });
XFile? videoPath = await controller.stopVideoRecording(); XFile? videoPath = await controller?.stopVideoRecording();
await controller.pausePreview(); await controller?.pausePreview();
if (await pushMediaEditor(null, videoPath)) { if (await pushMediaEditor(null, videoPath)) {
return; return;
} }
@ -354,7 +387,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (cameraId >= gCameras.length) { if (cameraId >= gCameras.length || controller == null) {
return Center( return Center(
child: Text("No camera found."), child: Text("No camera found."),
); );
@ -379,7 +412,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
basePanY = details.localPosition.dy; basePanY = details.localPosition.dy;
baseScaleFactor = scaleFactor; baseScaleFactor = scaleFactor;
}); });
print("onLongPressDown");
// Get the position of the pointer // Get the position of the pointer
RenderBox renderBox = RenderBox renderBox =
keyTriggerButton.currentContext?.findRenderObject() as RenderBox; keyTriggerButton.currentContext?.findRenderObject() as RenderBox;
@ -405,7 +437,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
children: [ children: [
if (!galleryLoadedImageIsShown) if (!galleryLoadedImageIsShown)
CameraPreviewWidget( CameraPreviewWidget(
controller: controller, controller: controller!,
screenshotController: screenshotController, screenshotController: screenshotController,
isFront: isFront, isFront: isFront,
), ),
@ -452,10 +484,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
: Colors.white.withAlpha(160), : Colors.white.withAlpha(160),
onPressed: () async { onPressed: () async {
if (isFlashOn) { if (isFlashOn) {
controller.setFlashMode(FlashMode.off); controller?.setFlashMode(FlashMode.off);
isFlashOn = false; isFlashOn = false;
} else { } else {
controller.setFlashMode(FlashMode.always); controller?.setFlashMode(FlashMode.always);
isFlashOn = true; isFlashOn = true;
} }
setState(() {}); setState(() {});
@ -500,7 +532,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: Column( child: Column(
children: [ children: [
if (controller.value.isInitialized && if (controller!.value.isInitialized &&
isZoomAble && isZoomAble &&
!isFront && !isFront &&
!isVideoRecording) !isVideoRecording)
@ -510,7 +542,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
key: widget.key, key: widget.key,
scaleFactor: scaleFactor, scaleFactor: scaleFactor,
updateScaleFactor: updateScaleFactor, updateScaleFactor: updateScaleFactor,
controller: controller, controller: controller!,
), ),
), ),
const SizedBox(height: 30), const SizedBox(height: 30),
@ -570,6 +602,52 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
], ],
), ),
), ),
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<Color>(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) if (!sharePreviewIsShown && widget.sendTo != null)
Positioned( Positioned(
left: 5, left: 5,

View file

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:logging/logging.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/views/camera/components/save_to_gallery.dart'; import 'package:twonly/src/views/camera/components/save_to_gallery.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart'; import 'package:twonly/src/views/camera/image_editor/action_button.dart';
@ -59,16 +60,12 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
} else if (widget.videFilePath != null) { } else if (widget.videFilePath != null) {
videoController = videoController =
VideoPlayerController.file(File(widget.videFilePath!.path)); VideoPlayerController.file(File(widget.videFilePath!.path));
videoController?.addListener(() {
setState(() {});
});
videoController?.setLooping(true); videoController?.setLooping(true);
videoController?.initialize().then((_) { videoController?.initialize().then((_) {
videoController!.play(); videoController!.play();
setState(() {}); setState(() {});
}).catchError((Object error) { }).catchError((Object error) {
print(error); Logger("ui.share_image_editor").shout(error);
}); });
videoController?.play(); videoController?.play();
print(widget.videFilePath!.path); print(widget.videFilePath!.path);
@ -260,6 +257,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
), ),
); );
if (wasSend != null && wasSend && context.mounted) { if (wasSend != null && wasSend && context.mounted) {
// ignore: use_build_context_synchronously
Navigator.pop(context, true); Navigator.pop(context, true);
} }
} }
@ -321,7 +319,10 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
_isRealTwonly, _isRealTwonly,
maxShowTime, maxShowTime,
); );
Navigator.pop(context, true); if (context.mounted) {
// ignore: use_build_context_synchronously
Navigator.pop(context, true);
}
} }
@override @override