From c7a749a7567046f188a18c688e86a557af43c4f3 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Wed, 23 Dec 2020 11:49:00 +0100 Subject: [PATCH 01/22] Added maxVideoDuration to startVideoRecording --- .../camera_platform_interface/CHANGELOG.md | 4 ++++ .../method_channel/method_channel_camera.dart | 7 ++++-- .../platform_interface/camera_platform.dart | 3 ++- .../camera_platform_interface/pubspec.yaml | 2 +- .../method_channel_camera_test.dart | 23 +++++++++++++++++++ 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index ea9821e841f9..e071db517919 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.0 + +- Added maxVideoDuration to startVideoRecording for implementation of a limitation to the duration of a video recording + ## 1.0.4 - Added the torch option to the FlashMode enum, which when implemented indicates the flash light should be turned on continuously. diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 3bf996fedb19..be26c781e3e7 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -146,10 +146,13 @@ class MethodChannelCamera extends CameraPlatform { _channel.invokeMethod('prepareForVideoRecording'); @override - Future startVideoRecording(int cameraId) async { + Future startVideoRecording(int cameraId, {Duration maxVideoDuration}) async { await _channel.invokeMethod( 'startVideoRecording', - {'cameraId': cameraId}, + { + 'cameraId': cameraId, + 'maxVideoDuration': maxVideoDuration?.inMilliseconds, + }, ); } diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 6f96079dc55c..fd862e452caf 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -88,8 +88,9 @@ abstract class CameraPlatform extends PlatformInterface { /// Starts a video recording. /// + /// The length of the recording can be limited by defining the [maxVideoDuration] /// The video is returned as a [XFile] after calling [stopVideoRecording]. - Future startVideoRecording(int cameraId) { + Future startVideoRecording(int cameraId, {Duration maxVideoDuration}) { throw UnimplementedError('startVideoRecording() is not implemented.'); } diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 8cb643e84ca6..b6933314d41d 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the camera plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.0.4 +version: 1.1.0 dependencies: flutter: diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index c461b1fd583c..6e60e402d050 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -411,6 +411,29 @@ void main() { expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { 'cameraId': cameraId, + 'maxVideoDuration': null, + }), + ]); + }); + + test('Should pass maxVideoDuration when starting recording a video', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startVideoRecording': null}, + ); + + // Act + await camera.startVideoRecording( + cameraId, + maxVideoDuration: Duration(seconds: 10), + ); + + // Assert + expect(channel.log, [ + isMethodCall('startVideoRecording', arguments: { + 'cameraId': cameraId, + 'maxVideoDuration': 10000 }), ]); }); From 131918d820acca7c6a74a626cd2c323b3225c345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl?= <32639467+danielroek@users.noreply.github.com> Date: Wed, 23 Dec 2020 14:43:35 +0100 Subject: [PATCH 02/22] updated documentation Co-authored-by: Maurits van Beusekom --- packages/camera/camera_platform_interface/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index e071db517919..d117e1c0eba4 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ ## 1.1.0 -- Added maxVideoDuration to startVideoRecording for implementation of a limitation to the duration of a video recording +- Added an optional `maxVideoDuration` parameter to the `startVideoRecording` method, which allows implementations to limit the duration of a video recording. ## 1.0.4 From 9b3ae14914369868d35082449e1b4dbb77c8ee61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl?= <32639467+danielroek@users.noreply.github.com> Date: Wed, 23 Dec 2020 14:43:43 +0100 Subject: [PATCH 03/22] updated documentation Co-authored-by: Maurits van Beusekom --- .../lib/src/platform_interface/camera_platform.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index fd862e452caf..73036d577bfd 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -88,7 +88,7 @@ abstract class CameraPlatform extends PlatformInterface { /// Starts a video recording. /// - /// The length of the recording can be limited by defining the [maxVideoDuration] + /// The length of the recording can be limited by specifying the [maxVideoDuration]. By default no maximum duration is specified, meaning the recording will continue until manually stopped. /// The video is returned as a [XFile] after calling [stopVideoRecording]. Future startVideoRecording(int cameraId, {Duration maxVideoDuration}) { throw UnimplementedError('startVideoRecording() is not implemented.'); From 3ca25df0e4f39f025cc2a0532223a8ab11836b88 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Wed, 23 Dec 2020 15:19:29 +0100 Subject: [PATCH 04/22] Fixed long line in docs --- .../lib/src/platform_interface/camera_platform.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 73036d577bfd..6c8e200c75c2 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -88,7 +88,9 @@ abstract class CameraPlatform extends PlatformInterface { /// Starts a video recording. /// - /// The length of the recording can be limited by specifying the [maxVideoDuration]. By default no maximum duration is specified, meaning the recording will continue until manually stopped. + /// The length of the recording can be limited by specifying the [maxVideoDuration]. + /// By default no maximum duration is specified, + /// meaning the recording will continue until manually stopped. /// The video is returned as a [XFile] after calling [stopVideoRecording]. Future startVideoRecording(int cameraId, {Duration maxVideoDuration}) { throw UnimplementedError('startVideoRecording() is not implemented.'); From 5e626b9e5678f42f41fa02d68ee612bfc216cc9d Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Wed, 23 Dec 2020 16:33:25 +0100 Subject: [PATCH 05/22] Formatting --- .../lib/src/method_channel/method_channel_camera.dart | 3 ++- .../method_channel/method_channel_camera_test.dart | 11 +++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index be26c781e3e7..bf2b3d3bd70a 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -146,7 +146,8 @@ class MethodChannelCamera extends CameraPlatform { _channel.invokeMethod('prepareForVideoRecording'); @override - Future startVideoRecording(int cameraId, {Duration maxVideoDuration}) async { + Future startVideoRecording(int cameraId, + {Duration maxVideoDuration}) async { await _channel.invokeMethod( 'startVideoRecording', { diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index 6e60e402d050..e3573759fea8 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -411,12 +411,13 @@ void main() { expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { 'cameraId': cameraId, - 'maxVideoDuration': null, + 'maxVideoDuration': null, }), ]); }); - test('Should pass maxVideoDuration when starting recording a video', () async { + test('Should pass maxVideoDuration when starting recording a video', + () async { // Arrange MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', @@ -431,10 +432,8 @@ void main() { // Assert expect(channel.log, [ - isMethodCall('startVideoRecording', arguments: { - 'cameraId': cameraId, - 'maxVideoDuration': 10000 - }), + isMethodCall('startVideoRecording', + arguments: {'cameraId': cameraId, 'maxVideoDuration': 10000}), ]); }); From 02811b7759855d31951299759a1bf0664808986d Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Thu, 24 Dec 2020 12:36:00 +0100 Subject: [PATCH 06/22] Started implementation for Android --- .../src/main/java/io/flutter/plugins/camera/Camera.java | 7 ++++--- .../io/flutter/plugins/camera/MethodCallHandlerImpl.java | 2 +- .../plugins/camera/media/MediaRecorderBuilder.java | 8 ++++++++ packages/camera/camera/example/lib/main.dart | 3 ++- packages/camera/camera/lib/src/camera_controller.dart | 5 +++-- packages/camera/camera/pubspec.yaml | 3 ++- packages/camera/camera/test/camera_test.dart | 3 ++- 7 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 3e8bbc7b295b..94125c2a33e8 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -124,7 +124,7 @@ public void onOrientationChanged(int i) { characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)); } - private void prepareMediaRecorder(String outputFilePath) throws IOException { + private void prepareMediaRecorder(String outputFilePath, int maxVideoDuration) throws IOException { if (mediaRecorder != null) { mediaRecorder.release(); } @@ -133,6 +133,7 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { new MediaRecorderBuilder(recordingProfile, outputFilePath) .setEnableAudio(enableAudio) .setMediaOrientation(getMediaOrientation()) + .setMaxVideoDuration(maxVideoDuration) .build(); } @@ -430,7 +431,7 @@ private void createCaptureSession( cameraDevice.createCaptureSession(surfaces, callback, null); } - public void startVideoRecording(Result result) { + public void startVideoRecording(Result result, Integer maxVideoDuration) { final File outputDir = applicationContext.getCacheDir(); try { videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir); @@ -440,7 +441,7 @@ public void startVideoRecording(Result result) { } try { - prepareMediaRecorder(videoRecordingFile.getAbsolutePath()); + prepareMediaRecorder(videoRecordingFile.getAbsolutePath(), maxVideoDuration); recordingVideo = true; createCaptureSession( CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index 704504176518..fb5141c31093 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -105,7 +105,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) } case "startVideoRecording": { - camera.startVideoRecording(result); + camera.startVideoRecording(result, call.argument("maxVideoDuration")); break; } case "stopVideoRecording": diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java index b2309c83a4a5..e13107f72716 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java @@ -21,6 +21,7 @@ MediaRecorder makeMediaRecorder() { private boolean enableAudio; private int mediaOrientation; + private int maxVideoDuration; public MediaRecorderBuilder( @NonNull CamcorderProfile recordingProfile, @NonNull String outputFilePath) { @@ -46,6 +47,11 @@ public MediaRecorderBuilder setMediaOrientation(int orientation) { return this; } + public MediaRecorderBuilder setMaxVideoDuration(int maxVideoDuration) { + this.maxVideoDuration = maxVideoDuration; + return this; + } + public MediaRecorder build() throws IOException { MediaRecorder mediaRecorder = recorderFactory.makeMediaRecorder(); @@ -66,6 +72,8 @@ public MediaRecorder build() throws IOException { mediaRecorder.setOutputFile(outputFilePath); mediaRecorder.setOrientationHint(this.mediaOrientation); + mediaRecorder.setMaxDuration(maxVideoDuration); + mediaRecorder.prepare(); return mediaRecorder; diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 049141d3935e..95eb1cbf2c14 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -438,7 +438,8 @@ class _CameraExampleHomeState extends State } try { - await controller.startVideoRecording(); + await controller.startVideoRecording( + maxVideoDuration: const Duration(milliseconds: 5000)); } on CameraException catch (e) { _showCameraException(e); return; diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index b79975fbad8e..6da567a085e8 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -360,7 +360,7 @@ class CameraController extends ValueNotifier { /// /// The video is returned as a [XFile] after calling [stopVideoRecording]. /// Throws a [CameraException] if the capture fails. - Future startVideoRecording() async { + Future startVideoRecording({Duration maxVideoDuration}) async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -381,7 +381,8 @@ class CameraController extends ValueNotifier { } try { - await CameraPlatform.instance.startVideoRecording(_cameraId); + await CameraPlatform.instance + .startVideoRecording(_cameraId, maxVideoDuration: maxVideoDuration); value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); } on PlatformException catch (e) { throw CameraException(e.code, e.message); diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index d823dd383281..6658c109f13a 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -8,7 +8,8 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: flutter: sdk: flutter - camera_platform_interface: ^1.0.4 + camera_platform_interface: + path: ../camera_platform_interface dev_dependencies: path_provider: ^0.5.0 diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 43dec7374901..04d9d3d19f93 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -641,7 +641,8 @@ class MockCameraPlatform extends Mock : Future.value(mockTakePicture); @override - Future startVideoRecording(int cameraId) => + Future startVideoRecording(int cameraId, + {Duration maxVideoDuration}) => Future.value(mockVideoRecordingXFile); } From b8b07e2e24c757a2294e4a81f9ccda48db39cdef Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Thu, 24 Dec 2020 16:24:10 +0100 Subject: [PATCH 07/22] WIP: Started implementation of stream when time limit is reached --- .../io/flutter/plugins/camera/Camera.java | 6 ++++ .../flutter/plugins/camera/DartMessenger.java | 11 +++++++ .../lib/src/events/camera_event.dart | 33 +++++++++++++++++++ .../method_channel/method_channel_camera.dart | 17 ++++++++++ .../platform_interface/camera_platform.dart | 5 +++ 5 files changed, 72 insertions(+) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 94125c2a33e8..5ab7aa851569 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -1,5 +1,6 @@ package io.flutter.plugins.camera; +import static android.media.MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED; import static android.view.OrientationEventListener.ORIENTATION_UNKNOWN; import static io.flutter.plugins.camera.CameraUtils.computeBestPreviewSize; @@ -445,6 +446,11 @@ public void startVideoRecording(Result result, Integer maxVideoDuration) { recordingVideo = true; createCaptureSession( CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); + mediaRecorder.setOnInfoListener((mr, what, extra) -> { + if (what == MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { + dartMessenger.sendTimeLimitReachedEvent(videoRecordingFile.getPath()); + } + }); result.success(null); } catch (CameraAccessException | IOException e) { recordingVideo = false; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index 49f9d9a76de0..d21cddbe3218 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -14,6 +14,7 @@ enum EventType { ERROR, CAMERA_CLOSING, INITIALIZED, + MAX_TIME_LIMIT_REACHED, } DartMessenger(BinaryMessenger messenger, long cameraId) { @@ -31,6 +32,16 @@ void sendCameraInitializedEvent(Integer previewWidth, Integer previewHeight) { }); } + void sendTimeLimitReachedEvent(String path) { + this.send( + EventType.MAX_TIME_LIMIT_REACHED, + new HashMap() { + { + if (path != null) put("path", path); + } + }); + } + void sendCameraClosingEvent() { send(EventType.CAMERA_CLOSING); } diff --git a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart index ab3d45545f23..b952a865d9f5 100644 --- a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart +++ b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:camera_platform_interface/camera_platform_interface.dart'; + /// Generic Event coming from the native side of Camera. /// /// All [CameraEvent]s contain the `cameraId` that originated the event. This @@ -196,3 +198,34 @@ class CameraErrorEvent extends CameraEvent { @override int get hashCode => super.hashCode ^ description.hashCode; } + +class CameraTimeLimitReachedEvent extends CameraEvent { + final String path; + + CameraTimeLimitReachedEvent(int cameraId, this.path) : super(cameraId); + + /// Converts the supplied [Map] to an instance of the [CameraTimeLimitReachedEvent] + /// class. + CameraTimeLimitReachedEvent.fromJson(Map json) + : path = json['path'], + super(json['cameraId']); + + /// Converts the [CameraTimeLimitReachedEvent] instance into a [Map] instance that can be + /// serialized to JSON. + Map toJson() => { + 'cameraId': cameraId, + 'path': path, + }; + + @override + bool operator ==(Object other) => + identical(this, other) || + super == other && + other is CameraTimeLimitReachedEvent && + runtimeType == other.runtimeType && + path == other.path; + + @override + int get hashCode => super.hashCode ^ path.hashCode; + +} \ No newline at end of file diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index bf2b3d3bd70a..e45cf43f49e6 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -132,6 +132,11 @@ class MethodChannelCamera extends CameraPlatform { return _events(cameraId).whereType(); } + @override + Stream onCameraTimeLimitReached(int cameraId) { + return _events(cameraId).whereType(); + } + @override Future takePicture(int cameraId) async { String path = await _channel.invokeMethod( @@ -155,6 +160,12 @@ class MethodChannelCamera extends CameraPlatform { 'maxVideoDuration': maxVideoDuration?.inMilliseconds, }, ); + + if (maxVideoDuration != null) { + await onCameraTimeLimitReached(cameraId).first.then((value) { + debugPrint('received event'); + }); + } } @override @@ -271,6 +282,12 @@ class MethodChannelCamera extends CameraPlatform { call.arguments['previewHeight'], )); break; + case 'max_time_limit_reached': + cameraEventStreamController.add(CameraTimeLimitReachedEvent( + cameraId, + call.arguments['path'], + )); + break; case 'resolution_changed': cameraEventStreamController.add(CameraResolutionChangedEvent( cameraId, diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 6c8e200c75c2..74ee505ec5a6 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -76,6 +76,11 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('onCameraError() is not implemented.'); } + @override + Stream onCameraTimeLimitReached(int cameraId) { + throw UnimplementedError('onCameraTimeLimitReached() is not implemented.'); + } + /// Captures an image and returns the file where it was saved. Future takePicture(int cameraId) { throw UnimplementedError('takePicture() is not implemented.'); From 50edc53bdabd3728ef018caa7f88965694d10736 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Mon, 11 Jan 2021 10:03:03 +0100 Subject: [PATCH 08/22] Initial working implementation --- .../io/flutter/plugins/camera/Camera.java | 34 ++++++++++++------- packages/camera/camera/example/lib/main.dart | 3 +- .../camera/lib/src/camera_controller.dart | 17 +++++++++- packages/camera/camera/pubspec.yaml | 3 +- .../method_channel/method_channel_camera.dart | 2 +- 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 2f658bf76c01..8cf0ef9645fb 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -171,17 +171,25 @@ private void initFps(CameraCharacteristics cameraCharacteristics) { Log.i("Camera", "[FPS Range] is:" + fpsRange); } - private void prepareMediaRecorder(String outputFilePath, int maxVideoDuration) throws IOException { + private void prepareMediaRecorder(String outputFilePath, Integer maxVideoDuration) throws IOException { if (mediaRecorder != null) { mediaRecorder.release(); } - mediaRecorder = - new MediaRecorderBuilder(recordingProfile, outputFilePath) - .setEnableAudio(enableAudio) - .setMediaOrientation(getMediaOrientation()) - .setMaxVideoDuration(maxVideoDuration) - .build(); + if (maxVideoDuration != null) { + mediaRecorder = + new MediaRecorderBuilder(recordingProfile, outputFilePath) + .setEnableAudio(enableAudio) + .setMediaOrientation(getMediaOrientation()) + .setMaxVideoDuration(maxVideoDuration) + .build(); + } else { + mediaRecorder = + new MediaRecorderBuilder(recordingProfile, outputFilePath) + .setEnableAudio(enableAudio) + .setMediaOrientation(getMediaOrientation()) + .build(); + } } @SuppressLint("MissingPermission") @@ -595,11 +603,13 @@ public void startVideoRecording(Result result, Integer maxVideoDuration) { recordingVideo = true; createCaptureSession( CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); - mediaRecorder.setOnInfoListener((mr, what, extra) -> { - if (what == MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { - dartMessenger.sendTimeLimitReachedEvent(videoRecordingFile.getPath()); - } - }); + if(maxVideoDuration != null) { + mediaRecorder.setOnInfoListener((mr, what, extra) -> { + if (what == MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { + dartMessenger.sendTimeLimitReachedEvent(videoRecordingFile.getPath()); + } + }); + } result.success(null); } catch (CameraAccessException | IOException e) { recordingVideo = false; diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 55417f0a1f8a..6ab169ee8bf4 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -607,7 +607,8 @@ class _CameraExampleHomeState extends State try { await controller.startVideoRecording( - maxVideoDuration: const Duration(milliseconds: 5000)); + maxVideoDuration: null //const Duration(milliseconds: 5000), + ); } on CameraException catch (e) { _showCameraException(e); return; diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index ea49e4390813..f6bc40cf8e81 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -381,7 +381,7 @@ class CameraController extends ValueNotifier { /// /// The video is returned as a [XFile] after calling [stopVideoRecording]. /// Throws a [CameraException] if the capture fails. - Future startVideoRecording({Duration maxVideoDuration}) async { + Future startVideoRecording({Duration maxVideoDuration}) async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -402,9 +402,24 @@ class CameraController extends ValueNotifier { } try { + Completer completer = Completer(); await CameraPlatform.instance .startVideoRecording(_cameraId, maxVideoDuration: maxVideoDuration); value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); + + if (maxVideoDuration != null) { + await CameraPlatform.instance + .onCameraTimeLimitReached(_cameraId) + .listen((event) { + debugPrint('Video recorded to: ${event.path}'); + completer.complete(XFile(event.path)); + value = + value.copyWith(isRecordingVideo: false, isRecordingPaused: false); + }); + return completer.future; + } else { + return null; + } } on PlatformException catch (e) { throw CameraException(e.code, e.message); } diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 50f504a8d0e8..cf25cc7f5a4e 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -8,7 +8,8 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: flutter: sdk: flutter - camera_platform_interface: ^1.2.0 + camera_platform_interface: #^1.5.0 + path: ../camera_platform_interface pedantic: ^1.8.0 dev_dependencies: diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 03ddfdc976e8..15d2670f4228 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -184,7 +184,7 @@ class MethodChannelCamera extends CameraPlatform { @override Stream onCameraTimeLimitReached(int cameraId) { - return _events(cameraId).whereType(); + return _cameraEvents(cameraId).whereType(); } @override From 6e31d7c0ba0ae7be9d65b2f4ca97d5d819f6a2d3 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Mon, 11 Jan 2021 12:06:46 +0100 Subject: [PATCH 09/22] Android implementation works --- .../io/flutter/plugins/camera/Camera.java | 11 ++++- packages/camera/camera/example/lib/main.dart | 7 ++- .../camera/lib/src/camera_controller.dart | 45 ++++++++++++------- .../lib/src/events/camera_event.dart | 6 +-- .../method_channel/method_channel_camera.dart | 18 +++----- 5 files changed, 54 insertions(+), 33 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 8cf0ef9645fb..5a9ef6303971 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -606,7 +606,16 @@ public void startVideoRecording(Result result, Integer maxVideoDuration) { if(maxVideoDuration != null) { mediaRecorder.setOnInfoListener((mr, what, extra) -> { if (what == MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { - dartMessenger.sendTimeLimitReachedEvent(videoRecordingFile.getPath()); + try { + dartMessenger.sendTimeLimitReachedEvent(videoRecordingFile.getAbsolutePath()); + cameraCaptureSession.abortCaptures(); + mediaRecorder.stop(); + recordingVideo = false; + videoRecordingFile = null; + startPreview(); + } catch (CameraAccessException e) { + // Ignore exceptions and try to continue (changes are camera session already aborted capture) + } } }); } diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 6ab169ee8bf4..baf59d13b570 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -607,8 +607,13 @@ class _CameraExampleHomeState extends State try { await controller.startVideoRecording( - maxVideoDuration: null //const Duration(milliseconds: 5000), + maxVideoDuration: const Duration(milliseconds: 5000), ); + debugPrint('recording started'); + controller.onCameraTimeLimitReachedEvent(onCameraTimeLimitReached: (file) { + debugPrint('onCameraTimeLimitReached ${file.path}'); + }); + debugPrint('listening'); } on CameraException catch (e) { _showCameraException(e); return; diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index f6bc40cf8e81..3557c0f098e4 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -381,7 +381,9 @@ class CameraController extends ValueNotifier { /// /// The video is returned as a [XFile] after calling [stopVideoRecording]. /// Throws a [CameraException] if the capture fails. - Future startVideoRecording({Duration maxVideoDuration}) async { + /// + /// TODO: Documentation: when maxVideoDuration listen to Stream with CameraTimeLimitReachedEvent + Future startVideoRecording({Duration maxVideoDuration}) async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -402,24 +404,9 @@ class CameraController extends ValueNotifier { } try { - Completer completer = Completer(); await CameraPlatform.instance .startVideoRecording(_cameraId, maxVideoDuration: maxVideoDuration); value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); - - if (maxVideoDuration != null) { - await CameraPlatform.instance - .onCameraTimeLimitReached(_cameraId) - .listen((event) { - debugPrint('Video recorded to: ${event.path}'); - completer.complete(XFile(event.path)); - value = - value.copyWith(isRecordingVideo: false, isRecordingPaused: false); - }); - return completer.future; - } else { - return null; - } } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -498,6 +485,32 @@ class CameraController extends ValueNotifier { } } + /// TODO: Documentation + void onCameraTimeLimitReachedEvent({onCameraTimeLimitReached}) { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'cameraTimeLimitReachedEventStream was called on uninitialized CameraController', + ); + } + if (!value.isRecordingVideo) { + throw CameraException( + 'No video is recording', + 'cameraTimeLimitReachedEventStream was called when no video is recording.', + ); + } + debugPrint('ping'); + + CameraPlatform.instance.onCameraTimeLimitReached(_cameraId).listen((event) { + debugPrint('onCameraTimeLimitReached'); + value = value.copyWith(isRecordingVideo: false); + onCameraTimeLimitReached(event.path); + }); + + debugPrint('pong'); + return; + } + /// Returns a widget showing a live camera preview. Widget buildPreview() { if (!value.isInitialized || _isDisposed) { diff --git a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart index 8b5503262801..29032b2bed8d 100644 --- a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart +++ b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart @@ -237,21 +237,21 @@ class CameraErrorEvent extends CameraEvent { } class CameraTimeLimitReachedEvent extends CameraEvent { - final String path; + final XFile path; CameraTimeLimitReachedEvent(int cameraId, this.path) : super(cameraId); /// Converts the supplied [Map] to an instance of the [CameraTimeLimitReachedEvent] /// class. CameraTimeLimitReachedEvent.fromJson(Map json) - : path = json['path'], + : path = XFile(json['path']), super(json['cameraId']); /// Converts the [CameraTimeLimitReachedEvent] instance into a [Map] instance that can be /// serialized to JSON. Map toJson() => { 'cameraId': cameraId, - 'path': path, + 'path': path.path, }; @override diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 15d2670f4228..107c843c36f6 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -162,6 +162,11 @@ class MethodChannelCamera extends CameraPlatform { .whereType(); } + @override + Stream onCameraTimeLimitReached(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + @override Future lockCaptureOrientation( int cameraId, DeviceOrientation orientation) async { @@ -182,11 +187,6 @@ class MethodChannelCamera extends CameraPlatform { ); } - @override - Stream onCameraTimeLimitReached(int cameraId) { - return _cameraEvents(cameraId).whereType(); - } - @override Future takePicture(int cameraId) async { String path = await _channel.invokeMethod( @@ -210,12 +210,6 @@ class MethodChannelCamera extends CameraPlatform { 'maxVideoDuration': maxVideoDuration?.inMilliseconds, }, ); - - if (maxVideoDuration != null) { - await onCameraTimeLimitReached(cameraId).first.then((value) { - debugPrint('received event'); - }); - } } @override @@ -435,7 +429,7 @@ class MethodChannelCamera extends CameraPlatform { case 'max_time_limit_reached': cameraEventStreamController.add(CameraTimeLimitReachedEvent( cameraId, - call.arguments['path'], + XFile(call.arguments['path']), )); break; case 'resolution_changed': From d3bab020aab21805ab5629078bf38c59f0f8ff9f Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Mon, 11 Jan 2021 12:36:45 +0100 Subject: [PATCH 10/22] Improved implementation --- packages/camera/camera/CHANGELOG.md | 4 ++ packages/camera/camera/README.md | 31 ++++++++++++- .../io/flutter/plugins/camera/Camera.java | 45 +++++++++---------- packages/camera/camera/example/lib/main.dart | 5 +-- .../camera/lib/src/camera_controller.dart | 2 +- packages/camera/camera/pubspec.yaml | 2 +- 6 files changed, 59 insertions(+), 30 deletions(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 2f9b5399d6dd..ca68b6a9fa0f 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.5 + +* Added maxVideoDuration to startVideoRecording to limit the length of a recording. + ## 0.6.4+5 * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. diff --git a/packages/camera/camera/README.md b/packages/camera/camera/README.md index f7163818aae3..bdf01c8b3a81 100644 --- a/packages/camera/camera/README.md +++ b/packages/camera/camera/README.md @@ -47,7 +47,7 @@ It's important to note that the `MediaRecorder` class is not working properly on ### Handling Lifecycle states -As of version [0.5.0](https://github.com/flutter/plugins/blob/master/packages/camera/CHANGELOG.md#050) of the camera plugin, lifecycle changes are no longer handled by the plugin. This means developers are now responsible to control camera resources when the lifecycle state is updated. Failure to do so might lead to unexpected behavior (for example as described in issue [#39109](https://github.com/flutter/flutter/issues/39109)). Handling lifecycle changes can be done by overriding the `didChangeAppLifecycleState` method like so: +As of version [0.5.0](https://github.com/flutter/plugins/blob/master/packages/camera/camera/CHANGELOG.md#050) of the camera plugin, lifecycle changes are no longer handled by the plugin. This means developers are now responsible to control camera resources when the lifecycle state is updated. Failure to do so might lead to unexpected behavior (for example as described in issue [#39109](https://github.com/flutter/flutter/issues/39109)). Handling lifecycle changes can be done by overriding the `didChangeAppLifecycleState` method like so: ```dart @override @@ -122,6 +122,35 @@ class _CameraAppState extends State { } ``` +As of version [0.6.5](https://github.com/flutter/plugins/blob/master/packages/camera/CHANGELOG.md#065) the startVideoRecording method can be used with the maxVideoDuration. To do this the result of the recording needs to be retrieved by calling controller.onCameraTimeLimitReachedEvent which accepts a callback to retrieve the XFile result. Like so: + +```dart + Future startVideoRecording() async { + if (!controller.value.isInitialized) { + showInSnackBar('Error: select a camera first.'); + return; + } + + if (controller.value.isRecordingVideo) { + // A recording is already started, do nothing. + return; + } + + try { + await controller.startVideoRecording( + maxVideoDuration: const Duration(milliseconds: 5000), + ); + controller.onCameraTimeLimitReachedEvent(onCameraTimeLimitReached: (XFile file) { + //Handle the XFile + debugPrint('onCameraTimeLimitReached ${file.path}'); + }); + } on CameraException catch (e) { + _showCameraException(e); + return; + } + } +``` + For a more elaborate usage example see [here](https://github.com/flutter/plugins/tree/master/packages/camera/example). *Note*: This plugin is still under development, and some APIs might not be available yet. diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 5a9ef6303971..6fd5e238f37b 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -176,20 +176,14 @@ private void prepareMediaRecorder(String outputFilePath, Integer maxVideoDuratio mediaRecorder.release(); } + MediaRecorderBuilder mediaRecorderBuilder = new MediaRecorderBuilder(recordingProfile, outputFilePath) + .setEnableAudio(enableAudio) + .setMediaOrientation(getMediaOrientation()); + if (maxVideoDuration != null) { - mediaRecorder = - new MediaRecorderBuilder(recordingProfile, outputFilePath) - .setEnableAudio(enableAudio) - .setMediaOrientation(getMediaOrientation()) - .setMaxVideoDuration(maxVideoDuration) - .build(); - } else { - mediaRecorder = - new MediaRecorderBuilder(recordingProfile, outputFilePath) - .setEnableAudio(enableAudio) - .setMediaOrientation(getMediaOrientation()) - .build(); + mediaRecorderBuilder.setMaxVideoDuration(maxVideoDuration); } + mediaRecorder = mediaRecorderBuilder.build(); } @SuppressLint("MissingPermission") @@ -608,13 +602,11 @@ public void startVideoRecording(Result result, Integer maxVideoDuration) { if (what == MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { try { dartMessenger.sendTimeLimitReachedEvent(videoRecordingFile.getAbsolutePath()); - cameraCaptureSession.abortCaptures(); - mediaRecorder.stop(); recordingVideo = false; videoRecordingFile = null; - startPreview(); + resetCaptureSession(); } catch (CameraAccessException e) { - // Ignore exceptions and try to continue (changes are camera session already aborted capture) + result.error("videoRecordingFailed", e.getMessage(), null); } } }); @@ -627,6 +619,18 @@ public void startVideoRecording(Result result, Integer maxVideoDuration) { } } + public void resetCaptureSession() throws CameraAccessException { + try { + cameraCaptureSession.abortCaptures(); + mediaRecorder.stop(); + } catch (CameraAccessException | IllegalStateException e) { + // Ignore exceptions and try to continue (changes are camera session already aborted capture) + } + + mediaRecorder.reset(); + startPreview(); + } + public void stopVideoRecording(@NonNull final Result result) { if (!recordingVideo) { result.success(null); @@ -636,15 +640,8 @@ public void stopVideoRecording(@NonNull final Result result) { try { recordingVideo = false; - try { - cameraCaptureSession.abortCaptures(); - mediaRecorder.stop(); - } catch (CameraAccessException | IllegalStateException e) { - // Ignore exceptions and try to continue (changes are camera session already aborted capture) - } + resetCaptureSession(); - mediaRecorder.reset(); - startPreview(); result.success(videoRecordingFile.getAbsolutePath()); videoRecordingFile = null; } catch (CameraAccessException | IllegalStateException e) { diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index baf59d13b570..f2f4b184adee 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -609,11 +609,10 @@ class _CameraExampleHomeState extends State await controller.startVideoRecording( maxVideoDuration: const Duration(milliseconds: 5000), ); - debugPrint('recording started'); - controller.onCameraTimeLimitReachedEvent(onCameraTimeLimitReached: (file) { + controller.onCameraTimeLimitReachedEvent(onCameraTimeLimitReached: (XFile file) { + //Handle the XFile debugPrint('onCameraTimeLimitReached ${file.path}'); }); - debugPrint('listening'); } on CameraException catch (e) { _showCameraException(e); return; diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 3557c0f098e4..d94bec89c4f5 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -501,7 +501,7 @@ class CameraController extends ValueNotifier { } debugPrint('ping'); - CameraPlatform.instance.onCameraTimeLimitReached(_cameraId).listen((event) { + CameraPlatform.instance.onCameraTimeLimitReached(_cameraId).first.then((event) { debugPrint('onCameraTimeLimitReached'); value = value.copyWith(isRecordingVideo: false); onCameraTimeLimitReached(event.path); diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index cf25cc7f5a4e..6fd3c9202f4e 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -2,7 +2,7 @@ name: camera description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.6.4+5 +version: 0.6.5 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: From ae7365c5cdae1f36407517043d100f59c0d19050 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Mon, 11 Jan 2021 12:38:33 +0100 Subject: [PATCH 11/22] Updated README order --- packages/camera/camera/README.md | 57 ++++++++++++++++---------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/packages/camera/camera/README.md b/packages/camera/camera/README.md index bdf01c8b3a81..ef7ac98e3503 100644 --- a/packages/camera/camera/README.md +++ b/packages/camera/camera/README.md @@ -66,6 +66,34 @@ As of version [0.5.0](https://github.com/flutter/plugins/blob/master/packages/ca } ``` +As of version [0.6.5](https://github.com/flutter/plugins/blob/master/packages/camera/CHANGELOG.md#065) the startVideoRecording method can be used with the maxVideoDuration. To do this the result of the recording needs to be retrieved by calling controller.onCameraTimeLimitReachedEvent which accepts a callback to retrieve the XFile result. Like so: + +```dart + Future startVideoRecording() async { + if (!controller.value.isInitialized) { + showInSnackBar('Error: select a camera first.'); + return; + } + + if (controller.value.isRecordingVideo) { + // A recording is already started, do nothing. + return; + } + + try { + await controller.startVideoRecording( + maxVideoDuration: const Duration(milliseconds: 5000), + ); + controller.onCameraTimeLimitReachedEvent(onCameraTimeLimitReached: (XFile file) { + //Handle the XFile + debugPrint('onCameraTimeLimitReached ${file.path}'); + }); + } on CameraException catch (e) { + _showCameraException(e); + return; + } + } +``` ### Example Here is a small example flutter app displaying a full screen camera preview. @@ -122,35 +150,6 @@ class _CameraAppState extends State { } ``` -As of version [0.6.5](https://github.com/flutter/plugins/blob/master/packages/camera/CHANGELOG.md#065) the startVideoRecording method can be used with the maxVideoDuration. To do this the result of the recording needs to be retrieved by calling controller.onCameraTimeLimitReachedEvent which accepts a callback to retrieve the XFile result. Like so: - -```dart - Future startVideoRecording() async { - if (!controller.value.isInitialized) { - showInSnackBar('Error: select a camera first.'); - return; - } - - if (controller.value.isRecordingVideo) { - // A recording is already started, do nothing. - return; - } - - try { - await controller.startVideoRecording( - maxVideoDuration: const Duration(milliseconds: 5000), - ); - controller.onCameraTimeLimitReachedEvent(onCameraTimeLimitReached: (XFile file) { - //Handle the XFile - debugPrint('onCameraTimeLimitReached ${file.path}'); - }); - } on CameraException catch (e) { - _showCameraException(e); - return; - } - } -``` - For a more elaborate usage example see [here](https://github.com/flutter/plugins/tree/master/packages/camera/example). *Note*: This plugin is still under development, and some APIs might not be available yet. From ae1b47c90e21bc02dc11b1bfb7fbddc155406807 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Mon, 11 Jan 2021 12:43:36 +0100 Subject: [PATCH 12/22] removed debugPrints --- packages/camera/camera/README.md | 1 - packages/camera/camera/example/lib/main.dart | 9 ++------- packages/camera/camera/lib/src/camera_controller.dart | 5 ----- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/camera/camera/README.md b/packages/camera/camera/README.md index ef7ac98e3503..c842785b680e 100644 --- a/packages/camera/camera/README.md +++ b/packages/camera/camera/README.md @@ -86,7 +86,6 @@ As of version [0.6.5](https://github.com/flutter/plugins/blob/master/packages/ca ); controller.onCameraTimeLimitReachedEvent(onCameraTimeLimitReached: (XFile file) { //Handle the XFile - debugPrint('onCameraTimeLimitReached ${file.path}'); }); } on CameraException catch (e) { _showCameraException(e); diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index f2f4b184adee..b63f0724bd22 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -606,13 +606,8 @@ class _CameraExampleHomeState extends State } try { - await controller.startVideoRecording( - maxVideoDuration: const Duration(milliseconds: 5000), - ); - controller.onCameraTimeLimitReachedEvent(onCameraTimeLimitReached: (XFile file) { - //Handle the XFile - debugPrint('onCameraTimeLimitReached ${file.path}'); - }); + await controller.startVideoRecording(); + } on CameraException catch (e) { _showCameraException(e); return; diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index d94bec89c4f5..1e5acc3f3d63 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -499,16 +499,11 @@ class CameraController extends ValueNotifier { 'cameraTimeLimitReachedEventStream was called when no video is recording.', ); } - debugPrint('ping'); CameraPlatform.instance.onCameraTimeLimitReached(_cameraId).first.then((event) { - debugPrint('onCameraTimeLimitReached'); value = value.copyWith(isRecordingVideo: false); onCameraTimeLimitReached(event.path); }); - - debugPrint('pong'); - return; } /// Returns a widget showing a live camera preview. From 4037b5e07d9190bfc81c10e0c1a99ab90da6d335 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Mon, 11 Jan 2021 12:45:05 +0100 Subject: [PATCH 13/22] Fixed url in README.md --- packages/camera/camera/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera/README.md b/packages/camera/camera/README.md index c842785b680e..5186fe1c4aeb 100644 --- a/packages/camera/camera/README.md +++ b/packages/camera/camera/README.md @@ -66,7 +66,7 @@ As of version [0.5.0](https://github.com/flutter/plugins/blob/master/packages/ca } ``` -As of version [0.6.5](https://github.com/flutter/plugins/blob/master/packages/camera/CHANGELOG.md#065) the startVideoRecording method can be used with the maxVideoDuration. To do this the result of the recording needs to be retrieved by calling controller.onCameraTimeLimitReachedEvent which accepts a callback to retrieve the XFile result. Like so: +As of version [0.6.5](https://github.com/flutter/plugins/blob/master/packages/camera/camera/CHANGELOG.md#065) the startVideoRecording method can be used with the maxVideoDuration. To do this the result of the recording needs to be retrieved by calling controller.onCameraTimeLimitReachedEvent which accepts a callback to retrieve the XFile result. Like so: ```dart Future startVideoRecording() async { @@ -149,7 +149,7 @@ class _CameraAppState extends State { } ``` -For a more elaborate usage example see [here](https://github.com/flutter/plugins/tree/master/packages/camera/example). +For a more elaborate usage example see [here](https://github.com/flutter/plugins/tree/master/packages/camera/camera/example). *Note*: This plugin is still under development, and some APIs might not be available yet. [Feedback welcome](https://github.com/flutter/flutter/issues) and From 36acc32831084f1c22e83f3a6280b1132cb0ccdc Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Mon, 11 Jan 2021 12:51:18 +0100 Subject: [PATCH 14/22] Formatting --- packages/camera/camera/example/lib/main.dart | 1 - .../camera/lib/src/camera_controller.dart | 5 ++++- .../lib/src/events/camera_event.dart | 17 ++++++++--------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index b63f0724bd22..5324e3d09383 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -607,7 +607,6 @@ class _CameraExampleHomeState extends State try { await controller.startVideoRecording(); - } on CameraException catch (e) { _showCameraException(e); return; diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 1e5acc3f3d63..743fc424a989 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -500,7 +500,10 @@ class CameraController extends ValueNotifier { ); } - CameraPlatform.instance.onCameraTimeLimitReached(_cameraId).first.then((event) { + CameraPlatform.instance + .onCameraTimeLimitReached(_cameraId) + .first + .then((event) { value = value.copyWith(isRecordingVideo: false); onCameraTimeLimitReached(event.path); }); diff --git a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart index 29032b2bed8d..14369d8f4a2a 100644 --- a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart +++ b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart @@ -250,19 +250,18 @@ class CameraTimeLimitReachedEvent extends CameraEvent { /// Converts the [CameraTimeLimitReachedEvent] instance into a [Map] instance that can be /// serialized to JSON. Map toJson() => { - 'cameraId': cameraId, - 'path': path.path, - }; + 'cameraId': cameraId, + 'path': path.path, + }; @override bool operator ==(Object other) => identical(this, other) || - super == other && - other is CameraTimeLimitReachedEvent && - runtimeType == other.runtimeType && - path == other.path; + super == other && + other is CameraTimeLimitReachedEvent && + runtimeType == other.runtimeType && + path == other.path; @override int get hashCode => super.hashCode ^ path.hashCode; - -} \ No newline at end of file +} From 09dad16d21de3c36d876e10418c426e742c3bbdb Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Wed, 13 Jan 2021 10:18:02 +0100 Subject: [PATCH 15/22] Implemented Java feedback --- .../src/main/java/io/flutter/plugins/camera/Camera.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index c25fde98ed79..34c18bfc2484 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -647,8 +647,8 @@ public void resetCaptureSession() throws CameraAccessException { try { cameraCaptureSession.abortCaptures(); mediaRecorder.stop(); - } catch (CameraAccessException | IllegalStateException e) { - // Ignore exceptions and try to continue (changes are camera session already aborted capture) + } catch (IllegalStateException e) { + // Ignore exceptions and try to continue (chances are camera session already aborted capture) } mediaRecorder.reset(); @@ -668,7 +668,7 @@ public void stopVideoRecording(@NonNull final Result result) { result.success(videoRecordingFile.getAbsolutePath()); videoRecordingFile = null; - } catch (CameraAccessException | IllegalStateException e) { + } catch (CameraAccessException e) { result.error("videoRecordingFailed", e.getMessage(), null); } } From 771f116cab8034419549ac8c38aaf4f968fa2560 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Wed, 13 Jan 2021 13:24:56 +0100 Subject: [PATCH 16/22] Implemented Event and Stream to notify about videoRecording --- .../io/flutter/plugins/camera/Camera.java | 8 ++++-- .../flutter/plugins/camera/DartMessenger.java | 7 ++--- packages/camera/camera/example/lib/main.dart | 6 ++++- packages/camera/camera/lib/camera.dart | 1 + .../camera/lib/src/camera_controller.dart | 25 +++++++---------- packages/camera/camera/test/camera_test.dart | 18 +++++++++++++ .../lib/src/events/camera_event.dart | 27 +++++++++++-------- .../method_channel/method_channel_camera.dart | 17 +++++++----- .../platform_interface/camera_platform.dart | 10 +++---- 9 files changed, 75 insertions(+), 44 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 34c18bfc2484..47dd80bcb000 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -107,6 +107,7 @@ public class Camera { private int exposureOffset; private boolean useAutoFocus = true; private Range fpsRange; + private Integer maxDurationLimit; private static final HashMap supportedImageFormats; // Current supported outputs @@ -609,6 +610,7 @@ private void unlockAutoFocus() { public void startVideoRecording(Result result, Integer maxVideoDuration) { final File outputDir = applicationContext.getCacheDir(); + maxDurationLimit = maxVideoDuration; try { videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir); } catch (IOException | SecurityException e) { @@ -625,9 +627,10 @@ public void startVideoRecording(Result result, Integer maxVideoDuration) { mediaRecorder.setOnInfoListener((mr, what, extra) -> { if (what == MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { try { - dartMessenger.sendTimeLimitReachedEvent(videoRecordingFile.getAbsolutePath()); + dartMessenger.sendVideoRecordedEvent(videoRecordingFile.getAbsolutePath(), maxVideoDuration); recordingVideo = false; videoRecordingFile = null; + maxDurationLimit = null; resetCaptureSession(); } catch (CameraAccessException e) { result.error("videoRecordingFailed", e.getMessage(), null); @@ -665,8 +668,9 @@ public void stopVideoRecording(@NonNull final Result result) { recordingVideo = false; resetCaptureSession(); - + dartMessenger.sendVideoRecordedEvent(videoRecordingFile.getAbsolutePath(), maxDurationLimit); result.success(videoRecordingFile.getAbsolutePath()); + maxDurationLimit = null; videoRecordingFile = null; } catch (CameraAccessException e) { result.error("videoRecordingFailed", e.getMessage(), null); diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index 6854ef84fc96..436ebe93e07d 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -20,7 +20,7 @@ enum EventType { ERROR, CAMERA_CLOSING, INITIALIZED, - MAX_TIME_LIMIT_REACHED, + VIDEO_RECORDED, } DartMessenger(BinaryMessenger messenger, long cameraId) { @@ -54,12 +54,13 @@ void sendCameraInitializedEvent( }); } - void sendTimeLimitReachedEvent(String path) { + void sendVideoRecordedEvent(String path, Integer maxVideoDuration) { this.send( - EventType.MAX_TIME_LIMIT_REACHED, + EventType.VIDEO_RECORDED, new HashMap() { { if (path != null) put("path", path); + if (maxVideoDuration != null) put("maxVideoDuration", maxVideoDuration); } }); } diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 681a45172816..62f7dfbb5d72 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -571,6 +571,9 @@ class _CameraExampleHomeState extends State try { await controller.initialize(); + controller.onVideoRecordedEvent().listen((VideoRecordedEvent event) { + // Handle VideoRecordedEvent + }); _minAvailableExposureOffset = await controller.getMinExposureOffset(); _maxAvailableExposureOffset = await controller.getMaxExposureOffset(); _maxAvailableZoom = await controller.getMaxZoomLevel(); @@ -698,7 +701,8 @@ class _CameraExampleHomeState extends State } try { - await controller.startVideoRecording(); + await controller.startVideoRecording( + maxVideoDuration: Duration(seconds: 2)); } on CameraException catch (e) { _showCameraException(e); return; diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index d6e32affdd7a..d33ba5fd03dd 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -11,6 +11,7 @@ export 'package:camera_platform_interface/camera_platform_interface.dart' CameraDescription, CameraException, CameraLensDirection, + VideoRecordedEvent, FlashMode, ExposureMode, FocusMode, diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index b1d37d23eb43..e0e9e892ad09 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -435,6 +435,14 @@ class CameraController extends ValueNotifier { await CameraPlatform.instance .startVideoRecording(_cameraId, maxVideoDuration: maxVideoDuration); value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); + + // Listen for end to update isRecordingVideo in CameraValue. + CameraPlatform.instance + .onVideoRecordedEvent(_cameraId).asBroadcastStream() + .listen((event) { + value = value.copyWith(isRecordingVideo: false); + }); + debugPrint('started'); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -514,27 +522,14 @@ class CameraController extends ValueNotifier { } /// TODO: Documentation - void onCameraTimeLimitReachedEvent({onCameraTimeLimitReached}) { + Stream onVideoRecordedEvent() { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', 'cameraTimeLimitReachedEventStream was called on uninitialized CameraController', ); } - if (!value.isRecordingVideo) { - throw CameraException( - 'No video is recording', - 'cameraTimeLimitReachedEventStream was called when no video is recording.', - ); - } - - CameraPlatform.instance - .onCameraTimeLimitReached(_cameraId) - .first - .then((event) { - value = value.copyWith(isRecordingVideo: false); - onCameraTimeLimitReached(event.path); - }); + return CameraPlatform.instance.onVideoRecordedEvent(_cameraId); } /// Returns a widget showing a live camera preview. diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 1cea609d1741..a7bf43ea2cfd 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1021,6 +1021,24 @@ void main() { .setExposureOffset(cameraController.cameraId, -0.4)) .called(4); }); + + // test('Record video with time limit', () async { + // CameraController cameraController = CameraController( + // CameraDescription( + // name: 'cam', + // lensDirection: CameraLensDirection.back, + // sensorOrientation: 90), + // ResolutionPreset.max); + // await cameraController.initialize(); + // cameraController.onVideoRecordedEvent().listen((VideoRecordedEvent event) { + // debugPrint('VideoRecordedEvent received'); + // }); + // await cameraController.startVideoRecording(maxVideoDuration: Duration(seconds: 2)); + // // OR + // await cameraController.startVideoRecording(); + // await Future.delayed(Duration(milliseconds: 500)); + // await cameraController.stopVideoRecording(); + // }); }); } diff --git a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart index 14369d8f4a2a..c27a63267db2 100644 --- a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart +++ b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart @@ -236,32 +236,37 @@ class CameraErrorEvent extends CameraEvent { int get hashCode => super.hashCode ^ description.hashCode; } -class CameraTimeLimitReachedEvent extends CameraEvent { - final XFile path; +class VideoRecordedEvent extends CameraEvent { + final XFile file; + final Duration maxVideoDuration; - CameraTimeLimitReachedEvent(int cameraId, this.path) : super(cameraId); + VideoRecordedEvent(int cameraId, this.file, this.maxVideoDuration) : super(cameraId); - /// Converts the supplied [Map] to an instance of the [CameraTimeLimitReachedEvent] + /// Converts the supplied [Map] to an instance of the [VideoRecordedEvent] /// class. - CameraTimeLimitReachedEvent.fromJson(Map json) - : path = XFile(json['path']), + VideoRecordedEvent.fromJson(Map json) + : file = XFile(json['path']), + maxVideoDuration = json['maxVideoDuration'] != null + ? Duration(milliseconds: json['maxVideoDuration'] as int) : null, super(json['cameraId']); - /// Converts the [CameraTimeLimitReachedEvent] instance into a [Map] instance that can be + /// Converts the [VideoRecordedEvent] instance into a [Map] instance that can be /// serialized to JSON. Map toJson() => { 'cameraId': cameraId, - 'path': path.path, + 'path': file.path, + 'maxVideoDuration': maxVideoDuration?.inMilliseconds }; @override bool operator ==(Object other) => identical(this, other) || super == other && - other is CameraTimeLimitReachedEvent && + other is VideoRecordedEvent && runtimeType == other.runtimeType && - path == other.path; + file == other.file && + maxVideoDuration == other.maxVideoDuration; @override - int get hashCode => super.hashCode ^ path.hashCode; + int get hashCode => super.hashCode ^ file.hashCode ^ maxVideoDuration.hashCode; } diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 107c843c36f6..0346b1c3aa81 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -157,14 +157,14 @@ class MethodChannelCamera extends CameraPlatform { } @override - Stream onDeviceOrientationChanged() { - return deviceEventStreamController.stream - .whereType(); + Stream onVideoRecordedEvent(int cameraId) { + return _cameraEvents(cameraId).whereType(); } @override - Stream onCameraTimeLimitReached(int cameraId) { - return _cameraEvents(cameraId).whereType(); + Stream onDeviceOrientationChanged() { + return deviceEventStreamController.stream + .whereType(); } @override @@ -426,10 +426,13 @@ class MethodChannelCamera extends CameraPlatform { call.arguments['focusPointSupported'], )); break; - case 'max_time_limit_reached': - cameraEventStreamController.add(CameraTimeLimitReachedEvent( + case 'video_recorded': + cameraEventStreamController.add(VideoRecordedEvent( cameraId, XFile(call.arguments['path']), + call.arguments['maxVideoDuration'] != null + ? Duration(milliseconds: call.arguments['maxVideoDuration']) + : null, )); break; case 'resolution_changed': diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 7ebe52169aa7..80f3227f824f 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -87,6 +87,11 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('onCameraError() is not implemented.'); } + /// The camera finished recording a video + Stream onVideoRecordedEvent(int cameraId) { + throw UnimplementedError('onCameraTimeLimitReached() is not implemented.'); + } + /// The device orientation changed. /// /// Implementations for this: @@ -108,11 +113,6 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('unlockCaptureOrientation() is not implemented.'); } - @override - Stream onCameraTimeLimitReached(int cameraId) { - throw UnimplementedError('onCameraTimeLimitReached() is not implemented.'); - } - /// Captures an image and returns the file where it was saved. Future takePicture(int cameraId) { throw UnimplementedError('takePicture() is not implemented.'); From 16e43fbbff7d99459d17ed0128ec99f0b1546b29 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Wed, 13 Jan 2021 14:57:55 +0100 Subject: [PATCH 17/22] stopVideoRecording now listens to VideoRecordedEvent --- .../main/java/io/flutter/plugins/camera/Camera.java | 1 - .../camera/camera/lib/src/camera_controller.dart | 13 +++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 47dd80bcb000..3ab854ca2719 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -669,7 +669,6 @@ public void stopVideoRecording(@NonNull final Result result) { resetCaptureSession(); dartMessenger.sendVideoRecordedEvent(videoRecordingFile.getAbsolutePath(), maxDurationLimit); - result.success(videoRecordingFile.getAbsolutePath()); maxDurationLimit = null; videoRecordingFile = null; } catch (CameraAccessException e) { diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index e0e9e892ad09..10a67e4c4a55 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -442,7 +442,6 @@ class CameraController extends ValueNotifier { .listen((event) { value = value.copyWith(isRecordingVideo: false); }); - debugPrint('started'); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -465,9 +464,15 @@ class CameraController extends ValueNotifier { ); } try { - XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); - value = value.copyWith(isRecordingVideo: false); - return file; + Completer completer = Completer(); + CameraPlatform.instance + .onVideoRecordedEvent(_cameraId).asBroadcastStream() + .listen((event) { + value = value.copyWith(isRecordingVideo: false); + completer.complete(event.file); + }); + await CameraPlatform.instance.stopVideoRecording(_cameraId); + return completer.future; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } From 7cb73efe3fb79390e8c65d35c911b84c9eea6db6 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Wed, 13 Jan 2021 15:40:57 +0100 Subject: [PATCH 18/22] Fixed future returning xFile --- packages/camera/camera/example/lib/main.dart | 4 +++- .../camera/camera/lib/src/camera_controller.dart | 14 +++----------- .../src/method_channel/method_channel_camera.dart | 9 ++++++--- .../camera/camera_platform_interface/pubspec.yaml | 2 +- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 62f7dfbb5d72..b02938e9c64d 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -573,6 +573,7 @@ class _CameraExampleHomeState extends State await controller.initialize(); controller.onVideoRecordedEvent().listen((VideoRecordedEvent event) { // Handle VideoRecordedEvent + debugPrint('event is stream ${event.file.path}'); }); _minAvailableExposureOffset = await controller.getMinExposureOffset(); _maxAvailableExposureOffset = await controller.getMaxExposureOffset(); @@ -666,6 +667,7 @@ class _CameraExampleHomeState extends State void onStopButtonPressed() { stopVideoRecording().then((file) { + debugPrint('file after future ${file.path}'); if (mounted) setState(() {}); if (file != null) { showInSnackBar('Video recorded to ${file.path}'); @@ -702,7 +704,7 @@ class _CameraExampleHomeState extends State try { await controller.startVideoRecording( - maxVideoDuration: Duration(seconds: 2)); + maxVideoDuration: Duration(seconds: 20)); } on CameraException catch (e) { _showCameraException(e); return; diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 10a67e4c4a55..59b4fd856cfc 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -438,7 +438,7 @@ class CameraController extends ValueNotifier { // Listen for end to update isRecordingVideo in CameraValue. CameraPlatform.instance - .onVideoRecordedEvent(_cameraId).asBroadcastStream() + .onVideoRecordedEvent(_cameraId) .listen((event) { value = value.copyWith(isRecordingVideo: false); }); @@ -464,15 +464,7 @@ class CameraController extends ValueNotifier { ); } try { - Completer completer = Completer(); - CameraPlatform.instance - .onVideoRecordedEvent(_cameraId).asBroadcastStream() - .listen((event) { - value = value.copyWith(isRecordingVideo: false); - completer.complete(event.file); - }); - await CameraPlatform.instance.stopVideoRecording(_cameraId); - return completer.future; + return CameraPlatform.instance.stopVideoRecording(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -534,7 +526,7 @@ class CameraController extends ValueNotifier { 'cameraTimeLimitReachedEventStream was called on uninitialized CameraController', ); } - return CameraPlatform.instance.onVideoRecordedEvent(_cameraId); + return CameraPlatform.instance.onVideoRecordedEvent(_cameraId).asBroadcastStream(); } /// Returns a widget showing a live camera preview. diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 0346b1c3aa81..331b98846714 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -14,6 +14,7 @@ import 'package:cross_file/cross_file.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; +import 'package:pedantic/pedantic.dart'; import 'package:stream_transform/stream_transform.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); @@ -214,11 +215,13 @@ class MethodChannelCamera extends CameraPlatform { @override Future stopVideoRecording(int cameraId) async { - String path = await _channel.invokeMethod( + Completer completer = Completer(); + unawaited(onVideoRecordedEvent(cameraId).first.then((event) => completer.complete(event.file))); + unawaited(_channel.invokeMethod( 'stopVideoRecording', {'cameraId': cameraId}, - ); - return XFile(path); + )); + return completer.future; } @override diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 2a8d7ce9abe1..295c42968238 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -12,13 +12,13 @@ dependencies: plugin_platform_interface: ^1.0.1 cross_file: ^0.1.0 stream_transform: ^1.2.0 + pedantic: ^1.8.0 dev_dependencies: flutter_test: sdk: flutter async: ^2.4.2 mockito: ^4.1.1 - pedantic: ^1.8.0 environment: sdk: ">=2.7.0 <3.0.0" From 759f16324283b565a0985afeb380bf4d35b395ce Mon Sep 17 00:00:00 2001 From: Daniel Roek Date: Wed, 3 Feb 2021 14:03:04 +0100 Subject: [PATCH 19/22] finished iOS implementation --- packages/camera/camera/example/lib/main.dart | 2 +- .../camera/camera/ios/Classes/CameraPlugin.m | 39 +++++++++++++++---- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index b02938e9c64d..0c0dfe558504 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -704,7 +704,7 @@ class _CameraExampleHomeState extends State try { await controller.startVideoRecording( - maxVideoDuration: Duration(seconds: 20)); + maxVideoDuration: Duration(seconds: 5)); } on CameraException catch (e) { _showCameraException(e); return; diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 298b906ace7b..f9b808c87cde 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -751,7 +751,7 @@ - (CVPixelBufferRef)copyPixelBuffer { return pixelBuffer; } -- (void)startVideoRecordingWithResult:(FlutterResult)result { +- (void)startVideoRecordingWithResult:(FlutterResult)result maxVideoDuration:(int64_t)maxVideoDuration { if (!_isRecording) { NSError *error; _videoRecordingPath = [self getTemporaryFilePathWithExtension:@"mp4" @@ -766,6 +766,15 @@ - (void)startVideoRecordingWithResult:(FlutterResult)result { result([FlutterError errorWithCode:@"IOError" message:@"Setup Writer Failed" details:nil]); return; } + if (maxVideoDuration != 0) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(maxVideoDuration * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ + if (self->_isRecording) { + [self stopVideoRecordingWithResult:nil maxVideoDuration:maxVideoDuration]; + } + }); + } + //TODO: Set a timer with maxVideoDuration and call stopVideoRecording + //TODO: send videoRecordedEvent to Dart with path and maxVideoDuration _isRecording = YES; _isRecordingPaused = NO; _videoTimeOffset = CMTimeMake(0, 1); @@ -778,18 +787,32 @@ - (void)startVideoRecordingWithResult:(FlutterResult)result { } } -- (void)stopVideoRecordingWithResult:(FlutterResult)result { +- (void)stopVideoRecordingWithResult:(FlutterResult)result maxVideoDuration:(int64_t)maxVideoDuration { if (_isRecording) { _isRecording = NO; if (_videoWriter.status != AVAssetWriterStatusUnknown) { [_videoWriter finishWritingWithCompletionHandler:^{ if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { - result(self->_videoRecordingPath); + if(result != nil){ + result(self->_videoRecordingPath); + } + [self->_methodChannel + invokeMethod:@"video_recorded" + arguments:@{ + @"path" : self->_videoRecordingPath, + @"maxVideoDuration" : @(maxVideoDuration), + }]; + self->_videoRecordingPath = nil; } else { - result([FlutterError errorWithCode:@"IOError" - message:@"AVAssetWriter could not finish writing!" - details:nil]); + if(result != nil) { + result([FlutterError errorWithCode:@"IOError" + message:@"AVAssetWriter could not finish writing!" + details:nil]); + } + [self->_methodChannel invokeMethod:errorMethod + arguments:@"AVAssetWriter could not finish writing!"]; + } }]; } @@ -1282,9 +1305,9 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [_camera setUpCaptureSessionForAudio]; result(nil); } else if ([@"startVideoRecording" isEqualToString:call.method]) { - [_camera startVideoRecordingWithResult:result]; + [_camera startVideoRecordingWithResult:result maxVideoDuration:((NSNumber *)call.arguments[@"maxVideoDuration"]).intValue]; } else if ([@"stopVideoRecording" isEqualToString:call.method]) { - [_camera stopVideoRecordingWithResult:result]; + [_camera stopVideoRecordingWithResult:result maxVideoDuration:0]; } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { [_camera pauseVideoRecordingWithResult:result]; } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { From 0b4270d5b9634837d84ca4d17c5a66df72cf5fd0 Mon Sep 17 00:00:00 2001 From: Daniel Roek Date: Wed, 3 Feb 2021 14:09:46 +0100 Subject: [PATCH 20/22] Fixed formatting --- .../io/flutter/plugins/camera/Camera.java | 40 +++++++----- packages/camera/camera/example/lib/main.dart | 2 +- .../camera/camera/ios/Classes/CameraPlugin.m | 65 ++++++++++--------- .../camera/lib/src/camera_controller.dart | 8 +-- .../lib/src/events/camera_event.dart | 9 ++- .../method_channel/method_channel_camera.dart | 4 +- 6 files changed, 70 insertions(+), 58 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 3ab854ca2719..46e568a88347 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -189,19 +189,21 @@ private void initFps(CameraCharacteristics cameraCharacteristics) { Log.i("Camera", "[FPS Range] is:" + fpsRange); } - private void prepareMediaRecorder(String outputFilePath, Integer maxVideoDuration) throws IOException { + private void prepareMediaRecorder(String outputFilePath, Integer maxVideoDuration) + throws IOException { if (mediaRecorder != null) { mediaRecorder.release(); } - MediaRecorderBuilder mediaRecorderBuilder = new MediaRecorderBuilder(recordingProfile, outputFilePath) + MediaRecorderBuilder mediaRecorderBuilder = + new MediaRecorderBuilder(recordingProfile, outputFilePath) .setEnableAudio(enableAudio) .setMediaOrientation(getMediaOrientation()); if (maxVideoDuration != null) { mediaRecorderBuilder.setMaxVideoDuration(maxVideoDuration); } - mediaRecorder = mediaRecorderBuilder.build(); + mediaRecorder = mediaRecorderBuilder.build(); } @SuppressLint("MissingPermission") @@ -623,21 +625,23 @@ public void startVideoRecording(Result result, Integer maxVideoDuration) { recordingVideo = true; createCaptureSession( CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); - if(maxVideoDuration != null) { - mediaRecorder.setOnInfoListener((mr, what, extra) -> { - if (what == MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { - try { - dartMessenger.sendVideoRecordedEvent(videoRecordingFile.getAbsolutePath(), maxVideoDuration); - recordingVideo = false; - videoRecordingFile = null; - maxDurationLimit = null; - resetCaptureSession(); - } catch (CameraAccessException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - } - } - }); - } + if (maxVideoDuration != null) { + mediaRecorder.setOnInfoListener( + (mr, what, extra) -> { + if (what == MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { + try { + dartMessenger.sendVideoRecordedEvent( + videoRecordingFile.getAbsolutePath(), maxVideoDuration); + recordingVideo = false; + videoRecordingFile = null; + maxDurationLimit = null; + resetCaptureSession(); + } catch (CameraAccessException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + } + } + }); + } result.success(null); } catch (CameraAccessException | IOException e) { recordingVideo = false; diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 0c0dfe558504..8212c413465d 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -572,7 +572,7 @@ class _CameraExampleHomeState extends State try { await controller.initialize(); controller.onVideoRecordedEvent().listen((VideoRecordedEvent event) { - // Handle VideoRecordedEvent + // Handle VideoRecordedEvent debugPrint('event is stream ${event.file.path}'); }); _minAvailableExposureOffset = await controller.getMinExposureOffset(); diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index f9b808c87cde..f333c8667111 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -751,7 +751,8 @@ - (CVPixelBufferRef)copyPixelBuffer { return pixelBuffer; } -- (void)startVideoRecordingWithResult:(FlutterResult)result maxVideoDuration:(int64_t)maxVideoDuration { +- (void)startVideoRecordingWithResult:(FlutterResult)result + maxVideoDuration:(int64_t)maxVideoDuration { if (!_isRecording) { NSError *error; _videoRecordingPath = [self getTemporaryFilePathWithExtension:@"mp4" @@ -766,15 +767,16 @@ - (void)startVideoRecordingWithResult:(FlutterResult)result maxVideoDuration:(in result([FlutterError errorWithCode:@"IOError" message:@"Setup Writer Failed" details:nil]); return; } - if (maxVideoDuration != 0) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(maxVideoDuration * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ - if (self->_isRecording) { - [self stopVideoRecordingWithResult:nil maxVideoDuration:maxVideoDuration]; - } - }); - } - //TODO: Set a timer with maxVideoDuration and call stopVideoRecording - //TODO: send videoRecordedEvent to Dart with path and maxVideoDuration + if (maxVideoDuration != 0) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(maxVideoDuration * NSEC_PER_MSEC)), + dispatch_get_main_queue(), ^{ + if (self->_isRecording) { + [self stopVideoRecordingWithResult:nil maxVideoDuration:maxVideoDuration]; + } + }); + } + // TODO: Set a timer with maxVideoDuration and call stopVideoRecording + // TODO: send videoRecordedEvent to Dart with path and maxVideoDuration _isRecording = YES; _isRecordingPaused = NO; _videoTimeOffset = CMTimeMake(0, 1); @@ -787,32 +789,31 @@ - (void)startVideoRecordingWithResult:(FlutterResult)result maxVideoDuration:(in } } -- (void)stopVideoRecordingWithResult:(FlutterResult)result maxVideoDuration:(int64_t)maxVideoDuration { +- (void)stopVideoRecordingWithResult:(FlutterResult)result + maxVideoDuration:(int64_t)maxVideoDuration { if (_isRecording) { _isRecording = NO; if (_videoWriter.status != AVAssetWriterStatusUnknown) { [_videoWriter finishWritingWithCompletionHandler:^{ if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { - if(result != nil){ - result(self->_videoRecordingPath); - } - [self->_methodChannel - invokeMethod:@"video_recorded" - arguments:@{ - @"path" : self->_videoRecordingPath, - @"maxVideoDuration" : @(maxVideoDuration), - }]; - + if (result != nil) { + result(self->_videoRecordingPath); + } + [self->_methodChannel invokeMethod:@"video_recorded" + arguments:@{ + @"path" : self->_videoRecordingPath, + @"maxVideoDuration" : @(maxVideoDuration), + }]; + self->_videoRecordingPath = nil; } else { - if(result != nil) { - result([FlutterError errorWithCode:@"IOError" - message:@"AVAssetWriter could not finish writing!" - details:nil]); - } - [self->_methodChannel invokeMethod:errorMethod - arguments:@"AVAssetWriter could not finish writing!"]; - + if (result != nil) { + result([FlutterError errorWithCode:@"IOError" + message:@"AVAssetWriter could not finish writing!" + details:nil]); + } + [self->_methodChannel invokeMethod:errorMethod + arguments:@"AVAssetWriter could not finish writing!"]; } }]; } @@ -1305,9 +1306,11 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [_camera setUpCaptureSessionForAudio]; result(nil); } else if ([@"startVideoRecording" isEqualToString:call.method]) { - [_camera startVideoRecordingWithResult:result maxVideoDuration:((NSNumber *)call.arguments[@"maxVideoDuration"]).intValue]; + [_camera + startVideoRecordingWithResult:result + maxVideoDuration:((NSNumber *)call.arguments[@"maxVideoDuration"]).intValue]; } else if ([@"stopVideoRecording" isEqualToString:call.method]) { - [_camera stopVideoRecordingWithResult:result maxVideoDuration:0]; + [_camera stopVideoRecordingWithResult:result maxVideoDuration:0]; } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { [_camera pauseVideoRecordingWithResult:result]; } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 59b4fd856cfc..5f88976552fa 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -437,9 +437,7 @@ class CameraController extends ValueNotifier { value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); // Listen for end to update isRecordingVideo in CameraValue. - CameraPlatform.instance - .onVideoRecordedEvent(_cameraId) - .listen((event) { + CameraPlatform.instance.onVideoRecordedEvent(_cameraId).listen((event) { value = value.copyWith(isRecordingVideo: false); }); } on PlatformException catch (e) { @@ -526,7 +524,9 @@ class CameraController extends ValueNotifier { 'cameraTimeLimitReachedEventStream was called on uninitialized CameraController', ); } - return CameraPlatform.instance.onVideoRecordedEvent(_cameraId).asBroadcastStream(); + return CameraPlatform.instance + .onVideoRecordedEvent(_cameraId) + .asBroadcastStream(); } /// Returns a widget showing a live camera preview. diff --git a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart index c27a63267db2..3ac37748a4a3 100644 --- a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart +++ b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart @@ -240,14 +240,16 @@ class VideoRecordedEvent extends CameraEvent { final XFile file; final Duration maxVideoDuration; - VideoRecordedEvent(int cameraId, this.file, this.maxVideoDuration) : super(cameraId); + VideoRecordedEvent(int cameraId, this.file, this.maxVideoDuration) + : super(cameraId); /// Converts the supplied [Map] to an instance of the [VideoRecordedEvent] /// class. VideoRecordedEvent.fromJson(Map json) : file = XFile(json['path']), maxVideoDuration = json['maxVideoDuration'] != null - ? Duration(milliseconds: json['maxVideoDuration'] as int) : null, + ? Duration(milliseconds: json['maxVideoDuration'] as int) + : null, super(json['cameraId']); /// Converts the [VideoRecordedEvent] instance into a [Map] instance that can be @@ -268,5 +270,6 @@ class VideoRecordedEvent extends CameraEvent { maxVideoDuration == other.maxVideoDuration; @override - int get hashCode => super.hashCode ^ file.hashCode ^ maxVideoDuration.hashCode; + int get hashCode => + super.hashCode ^ file.hashCode ^ maxVideoDuration.hashCode; } diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 331b98846714..a2ff04785c81 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -216,7 +216,9 @@ class MethodChannelCamera extends CameraPlatform { @override Future stopVideoRecording(int cameraId) async { Completer completer = Completer(); - unawaited(onVideoRecordedEvent(cameraId).first.then((event) => completer.complete(event.file))); + unawaited(onVideoRecordedEvent(cameraId) + .first + .then((event) => completer.complete(event.file))); unawaited(_channel.invokeMethod( 'stopVideoRecording', {'cameraId': cameraId}, From 12231dab92983e50e71f7a55c3904d4f0448cad4 Mon Sep 17 00:00:00 2001 From: Daniel Roek Date: Wed, 3 Feb 2021 15:52:57 +0100 Subject: [PATCH 21/22] fixed formatting --- packages/camera/camera/example/lib/main.dart | 3 +- .../camera/camera/ios/Classes/CameraPlugin.m | 33 +++++++------------ .../lib/src/events/camera_event.dart | 1 - .../method_channel_camera_test.dart | 28 ++++++++++------ 4 files changed, 30 insertions(+), 35 deletions(-) diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 8212c413465d..29152078dbdf 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -703,8 +703,7 @@ class _CameraExampleHomeState extends State } try { - await controller.startVideoRecording( - maxVideoDuration: Duration(seconds: 5)); + await controller.startVideoRecording(maxVideoDuration: null); } on CameraException catch (e) { _showCameraException(e); return; diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index f333c8667111..7899fdd215c1 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -771,12 +771,10 @@ - (void)startVideoRecordingWithResult:(FlutterResult)result dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(maxVideoDuration * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ if (self->_isRecording) { - [self stopVideoRecordingWithResult:nil maxVideoDuration:maxVideoDuration]; + [self stopVideoRecording:maxVideoDuration]; } }); } - // TODO: Set a timer with maxVideoDuration and call stopVideoRecording - // TODO: send videoRecordedEvent to Dart with path and maxVideoDuration _isRecording = YES; _isRecordingPaused = NO; _videoTimeOffset = CMTimeMake(0, 1); @@ -789,16 +787,12 @@ - (void)startVideoRecordingWithResult:(FlutterResult)result } } -- (void)stopVideoRecordingWithResult:(FlutterResult)result - maxVideoDuration:(int64_t)maxVideoDuration { +- (void)stopVideoRecording:(int64_t)maxVideoDuration { if (_isRecording) { _isRecording = NO; if (_videoWriter.status != AVAssetWriterStatusUnknown) { [_videoWriter finishWritingWithCompletionHandler:^{ if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { - if (result != nil) { - result(self->_videoRecordingPath); - } [self->_methodChannel invokeMethod:@"video_recorded" arguments:@{ @"path" : self->_videoRecordingPath, @@ -807,22 +801,13 @@ - (void)stopVideoRecordingWithResult:(FlutterResult)result self->_videoRecordingPath = nil; } else { - if (result != nil) { - result([FlutterError errorWithCode:@"IOError" - message:@"AVAssetWriter could not finish writing!" - details:nil]); - } [self->_methodChannel invokeMethod:errorMethod arguments:@"AVAssetWriter could not finish writing!"]; } }]; } } else { - NSError *error = - [NSError errorWithDomain:NSCocoaErrorDomain - code:NSURLErrorResourceUnavailable - userInfo:@{NSLocalizedDescriptionKey : @"Video is not recording!"}]; - result(getFlutterError(error)); + [self->_methodChannel invokeMethod:errorMethod arguments:@"Video is not recording!"]; } } @@ -1306,11 +1291,15 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [_camera setUpCaptureSessionForAudio]; result(nil); } else if ([@"startVideoRecording" isEqualToString:call.method]) { - [_camera - startVideoRecordingWithResult:result - maxVideoDuration:((NSNumber *)call.arguments[@"maxVideoDuration"]).intValue]; + if ([call.arguments[@"maxVideoDuration"] class] != [NSNull class]) { + [_camera startVideoRecordingWithResult:result + maxVideoDuration:((NSNumber *)call.arguments[@"maxVideoDuration"]) + .intValue]; + } else { + [_camera startVideoRecordingWithResult:result maxVideoDuration:0]; + } } else if ([@"stopVideoRecording" isEqualToString:call.method]) { - [_camera stopVideoRecordingWithResult:result maxVideoDuration:0]; + [_camera stopVideoRecording:0]; } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { [_camera pauseVideoRecordingWithResult:result]; } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { diff --git a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart index 3ac37748a4a3..37783e451cce 100644 --- a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart +++ b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart @@ -266,7 +266,6 @@ class VideoRecordedEvent extends CameraEvent { super == other && other is VideoRecordedEvent && runtimeType == other.runtimeType && - file == other.file && maxVideoDuration == other.maxVideoDuration; @override diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index 7e9146344206..0c9309089943 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -512,21 +512,29 @@ void main() { test('Should stop a video recording and return the file', () async { // Arrange - MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'stopVideoRecording': '/test/path.mp4'}, + final Stream eventStream = + camera.onVideoRecordedEvent(cameraId); + final streamQueue = StreamQueue(eventStream); + + // Emit test events + final event = VideoRecordedEvent( + cameraId, + XFile('/test/path.mp4'), + Duration(milliseconds: 100), ); // Act - XFile file = await camera.stopVideoRecording(cameraId); + await camera.handleCameraMethodCall( + MethodCall('video_recorded', event.toJson()), cameraId); + final nextEvent = await streamQueue.next; // Assert - expect(channel.log, [ - isMethodCall('stopVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); - expect(file.path, '/test/path.mp4'); + expect(nextEvent.file.path, event.file.path); + expect(nextEvent.maxVideoDuration, event.maxVideoDuration); + expect(nextEvent.cameraId, event.cameraId); + + // Clean up + await streamQueue.cancel(); }); test('Should pause a video recording', () async { From 3b8d6edd9d50c59b7edf2ed6b9805b1c3ab07624 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Wed, 3 Feb 2021 16:54:02 +0100 Subject: [PATCH 22/22] Reverted platform_interface changes --- .../lib/src/events/camera_event.dart | 37 ------------------- .../method_channel/method_channel_camera.dart | 25 ++----------- .../platform_interface/camera_platform.dart | 5 --- .../method_channel_camera_test.dart | 28 +++++--------- 4 files changed, 13 insertions(+), 82 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart index 37783e451cce..d60a0a39f608 100644 --- a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart +++ b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart @@ -235,40 +235,3 @@ class CameraErrorEvent extends CameraEvent { @override int get hashCode => super.hashCode ^ description.hashCode; } - -class VideoRecordedEvent extends CameraEvent { - final XFile file; - final Duration maxVideoDuration; - - VideoRecordedEvent(int cameraId, this.file, this.maxVideoDuration) - : super(cameraId); - - /// Converts the supplied [Map] to an instance of the [VideoRecordedEvent] - /// class. - VideoRecordedEvent.fromJson(Map json) - : file = XFile(json['path']), - maxVideoDuration = json['maxVideoDuration'] != null - ? Duration(milliseconds: json['maxVideoDuration'] as int) - : null, - super(json['cameraId']); - - /// Converts the [VideoRecordedEvent] instance into a [Map] instance that can be - /// serialized to JSON. - Map toJson() => { - 'cameraId': cameraId, - 'path': file.path, - 'maxVideoDuration': maxVideoDuration?.inMilliseconds - }; - - @override - bool operator ==(Object other) => - identical(this, other) || - super == other && - other is VideoRecordedEvent && - runtimeType == other.runtimeType && - maxVideoDuration == other.maxVideoDuration; - - @override - int get hashCode => - super.hashCode ^ file.hashCode ^ maxVideoDuration.hashCode; -} diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index a2ff04785c81..e6f658c45365 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -14,7 +14,6 @@ import 'package:cross_file/cross_file.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; -import 'package:pedantic/pedantic.dart'; import 'package:stream_transform/stream_transform.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); @@ -157,11 +156,6 @@ class MethodChannelCamera extends CameraPlatform { return _cameraEvents(cameraId).whereType(); } - @override - Stream onVideoRecordedEvent(int cameraId) { - return _cameraEvents(cameraId).whereType(); - } - @override Stream onDeviceOrientationChanged() { return deviceEventStreamController.stream @@ -215,15 +209,11 @@ class MethodChannelCamera extends CameraPlatform { @override Future stopVideoRecording(int cameraId) async { - Completer completer = Completer(); - unawaited(onVideoRecordedEvent(cameraId) - .first - .then((event) => completer.complete(event.file))); - unawaited(_channel.invokeMethod( + String path = await _channel.invokeMethod( 'stopVideoRecording', {'cameraId': cameraId}, - )); - return completer.future; + ); + return XFile(path); } @override @@ -431,15 +421,6 @@ class MethodChannelCamera extends CameraPlatform { call.arguments['focusPointSupported'], )); break; - case 'video_recorded': - cameraEventStreamController.add(VideoRecordedEvent( - cameraId, - XFile(call.arguments['path']), - call.arguments['maxVideoDuration'] != null - ? Duration(milliseconds: call.arguments['maxVideoDuration']) - : null, - )); - break; case 'resolution_changed': cameraEventStreamController.add(CameraResolutionChangedEvent( cameraId, diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 80f3227f824f..c1d6e09c3263 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -87,11 +87,6 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('onCameraError() is not implemented.'); } - /// The camera finished recording a video - Stream onVideoRecordedEvent(int cameraId) { - throw UnimplementedError('onCameraTimeLimitReached() is not implemented.'); - } - /// The device orientation changed. /// /// Implementations for this: diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index 0c9309089943..7e9146344206 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -512,29 +512,21 @@ void main() { test('Should stop a video recording and return the file', () async { // Arrange - final Stream eventStream = - camera.onVideoRecordedEvent(cameraId); - final streamQueue = StreamQueue(eventStream); - - // Emit test events - final event = VideoRecordedEvent( - cameraId, - XFile('/test/path.mp4'), - Duration(milliseconds: 100), + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'stopVideoRecording': '/test/path.mp4'}, ); // Act - await camera.handleCameraMethodCall( - MethodCall('video_recorded', event.toJson()), cameraId); - final nextEvent = await streamQueue.next; + XFile file = await camera.stopVideoRecording(cameraId); // Assert - expect(nextEvent.file.path, event.file.path); - expect(nextEvent.maxVideoDuration, event.maxVideoDuration); - expect(nextEvent.cameraId, event.cameraId); - - // Clean up - await streamQueue.cancel(); + expect(channel.log, [ + isMethodCall('stopVideoRecording', arguments: { + 'cameraId': cameraId, + }), + ]); + expect(file.path, '/test/path.mp4'); }); test('Should pause a video recording', () async {