From 2394d40f4221027e291cab341f84fb9f46637f5f Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 5 Nov 2020 22:12:33 +0100 Subject: [PATCH 01/89] Move camera to camera/camera --- packages/camera/{ => camera}/CHANGELOG.md | 0 packages/camera/{ => camera}/LICENSE | 0 packages/camera/{ => camera}/README.md | 0 packages/camera/{ => camera}/android/build.gradle | 0 .../camera/{ => camera}/android/gradle.properties | 0 .../camera/{ => camera}/android/settings.gradle | 0 .../android/src/main/AndroidManifest.xml | 0 .../main/java/io/flutter/plugins/camera/Camera.java | 0 .../flutter/plugins/camera/CameraPermissions.java | 0 .../io/flutter/plugins/camera/CameraPlugin.java | 0 .../java/io/flutter/plugins/camera/CameraUtils.java | 0 .../io/flutter/plugins/camera/DartMessenger.java | 0 .../plugins/camera/MethodCallHandlerImpl.java | 0 .../plugins/camera/media/MediaRecorderBuilder.java | 0 .../plugins/camera/CameraPermissionsTest.java | 0 .../flutter/plugins/camera/DartMessengerTest.java | 0 .../camera/media/MediaRecorderBuilderTest.java | 0 packages/camera/{ => camera}/camera_android.iml | 0 packages/camera/{ => camera}/example/android.iml | 0 .../{ => camera}/example/android/app/build.gradle | 0 .../app/gradle/wrapper/gradle-wrapper.properties | 0 .../cameraexample/EmbeddingV1ActivityTest.java | 0 .../plugins/cameraexample/FlutterActivityTest.java | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../plugins/cameraexample/EmbeddingV1Activity.java | 0 .../app/src/main/res/drawable/launch_background.xml | 0 .../app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../android/app/src/main/res/values/styles.xml | 0 .../{ => camera}/example/android/build.gradle | 0 .../{ => camera}/example/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../{ => camera}/example/android/settings.gradle | 0 .../camera/{ => camera}/example/camera_example.iml | 0 .../{ => camera}/example/camera_example_android.iml | 0 .../example/integration_test/camera_test.dart | 0 .../example/ios/Flutter/AppFrameworkInfo.plist | 0 .../{ => camera}/example/ios/Flutter/Debug.xcconfig | 0 .../example/ios/Flutter/Release.xcconfig | 0 .../example/ios/Runner.xcodeproj/project.pbxproj | 0 .../project.xcworkspace/contents.xcworkspacedata | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../ios/Runner.xcworkspace/contents.xcworkspacedata | 0 .../{ => camera}/example/ios/Runner/AppDelegate.h | 0 .../{ => camera}/example/ios/Runner/AppDelegate.m | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../AppIcon.appiconset/Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../Assets.xcassets/LaunchImage.imageset/README.md | 0 .../ios/Runner/Base.lproj/LaunchScreen.storyboard | 0 .../example/ios/Runner/Base.lproj/Main.storyboard | 0 .../{ => camera}/example/ios/Runner/Info.plist | 0 .../camera/{ => camera}/example/ios/Runner/main.m | 0 packages/camera/{ => camera}/example/lib/main.dart | 0 packages/camera/{ => camera}/example/pubspec.yaml | 0 .../example/test_driver/integration_test.dart | 0 packages/camera/{ => camera}/ios/Assets/.gitkeep | 0 .../camera/{ => camera}/ios/Classes/CameraPlugin.h | 0 .../camera/{ => camera}/ios/Classes/CameraPlugin.m | 0 .../{ => camera}/ios/Tests/CameraPluginTests.m | 0 packages/camera/{ => camera}/ios/camera.podspec | 0 packages/camera/{ => camera}/lib/camera.dart | 0 packages/camera/{ => camera}/lib/camera_image.dart | 0 packages/camera/{ => camera}/pubspec.yaml | 0 83 files changed, 0 insertions(+), 0 deletions(-) rename packages/camera/{ => camera}/CHANGELOG.md (100%) rename packages/camera/{ => camera}/LICENSE (100%) rename packages/camera/{ => camera}/README.md (100%) rename packages/camera/{ => camera}/android/build.gradle (100%) rename packages/camera/{ => camera}/android/gradle.properties (100%) rename packages/camera/{ => camera}/android/settings.gradle (100%) rename packages/camera/{ => camera}/android/src/main/AndroidManifest.xml (100%) rename packages/camera/{ => camera}/android/src/main/java/io/flutter/plugins/camera/Camera.java (100%) rename packages/camera/{ => camera}/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java (100%) rename packages/camera/{ => camera}/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java (100%) rename packages/camera/{ => camera}/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java (100%) rename packages/camera/{ => camera}/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java (100%) rename packages/camera/{ => camera}/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java (100%) rename packages/camera/{ => camera}/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java (100%) rename packages/camera/{ => camera}/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java (100%) rename packages/camera/{ => camera}/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java (100%) rename packages/camera/{ => camera}/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java (100%) rename packages/camera/{ => camera}/camera_android.iml (100%) rename packages/camera/{ => camera}/example/android.iml (100%) rename packages/camera/{ => camera}/example/android/app/build.gradle (100%) rename packages/camera/{ => camera}/example/android/app/gradle/wrapper/gradle-wrapper.properties (100%) rename packages/camera/{ => camera}/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/EmbeddingV1ActivityTest.java (100%) rename packages/camera/{ => camera}/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/FlutterActivityTest.java (100%) rename packages/camera/{ => camera}/example/android/app/src/main/AndroidManifest.xml (100%) rename packages/camera/{ => camera}/example/android/app/src/main/java/io/flutter/plugins/cameraexample/EmbeddingV1Activity.java (100%) rename packages/camera/{ => camera}/example/android/app/src/main/res/drawable/launch_background.xml (100%) rename packages/camera/{ => camera}/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename packages/camera/{ => camera}/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename packages/camera/{ => camera}/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename packages/camera/{ => camera}/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename packages/camera/{ => camera}/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename packages/camera/{ => camera}/example/android/app/src/main/res/values/styles.xml (100%) rename packages/camera/{ => camera}/example/android/build.gradle (100%) rename packages/camera/{ => camera}/example/android/gradle.properties (100%) rename packages/camera/{ => camera}/example/android/gradle/wrapper/gradle-wrapper.properties (100%) rename packages/camera/{ => camera}/example/android/settings.gradle (100%) rename packages/camera/{ => camera}/example/camera_example.iml (100%) rename packages/camera/{ => camera}/example/camera_example_android.iml (100%) rename packages/camera/{ => camera}/example/integration_test/camera_test.dart (100%) rename packages/camera/{ => camera}/example/ios/Flutter/AppFrameworkInfo.plist (100%) rename packages/camera/{ => camera}/example/ios/Flutter/Debug.xcconfig (100%) rename packages/camera/{ => camera}/example/ios/Flutter/Release.xcconfig (100%) rename packages/camera/{ => camera}/example/ios/Runner.xcodeproj/project.pbxproj (100%) rename packages/camera/{ => camera}/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename packages/camera/{ => camera}/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename packages/camera/{ => camera}/example/ios/Runner.xcworkspace/contents.xcworkspacedata (100%) rename packages/camera/{ => camera}/example/ios/Runner/AppDelegate.h (100%) rename packages/camera/{ => camera}/example/ios/Runner/AppDelegate.m (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename packages/camera/{ => camera}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename packages/camera/{ => camera}/example/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename packages/camera/{ => camera}/example/ios/Runner/Base.lproj/Main.storyboard (100%) rename packages/camera/{ => camera}/example/ios/Runner/Info.plist (100%) rename packages/camera/{ => camera}/example/ios/Runner/main.m (100%) rename packages/camera/{ => camera}/example/lib/main.dart (100%) rename packages/camera/{ => camera}/example/pubspec.yaml (100%) rename packages/camera/{ => camera}/example/test_driver/integration_test.dart (100%) rename packages/camera/{ => camera}/ios/Assets/.gitkeep (100%) rename packages/camera/{ => camera}/ios/Classes/CameraPlugin.h (100%) rename packages/camera/{ => camera}/ios/Classes/CameraPlugin.m (100%) rename packages/camera/{ => camera}/ios/Tests/CameraPluginTests.m (100%) rename packages/camera/{ => camera}/ios/camera.podspec (100%) rename packages/camera/{ => camera}/lib/camera.dart (100%) rename packages/camera/{ => camera}/lib/camera_image.dart (100%) rename packages/camera/{ => camera}/pubspec.yaml (100%) diff --git a/packages/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md similarity index 100% rename from packages/camera/CHANGELOG.md rename to packages/camera/camera/CHANGELOG.md diff --git a/packages/camera/LICENSE b/packages/camera/camera/LICENSE similarity index 100% rename from packages/camera/LICENSE rename to packages/camera/camera/LICENSE diff --git a/packages/camera/README.md b/packages/camera/camera/README.md similarity index 100% rename from packages/camera/README.md rename to packages/camera/camera/README.md diff --git a/packages/camera/android/build.gradle b/packages/camera/camera/android/build.gradle similarity index 100% rename from packages/camera/android/build.gradle rename to packages/camera/camera/android/build.gradle diff --git a/packages/camera/android/gradle.properties b/packages/camera/camera/android/gradle.properties similarity index 100% rename from packages/camera/android/gradle.properties rename to packages/camera/camera/android/gradle.properties diff --git a/packages/camera/android/settings.gradle b/packages/camera/camera/android/settings.gradle similarity index 100% rename from packages/camera/android/settings.gradle rename to packages/camera/camera/android/settings.gradle diff --git a/packages/camera/android/src/main/AndroidManifest.xml b/packages/camera/camera/android/src/main/AndroidManifest.xml similarity index 100% rename from packages/camera/android/src/main/AndroidManifest.xml rename to packages/camera/camera/android/src/main/AndroidManifest.xml diff --git a/packages/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 similarity index 100% rename from packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java rename to packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java similarity index 100% rename from packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java rename to packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java similarity index 100% rename from packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java rename to packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java similarity index 100% rename from packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java rename to packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java diff --git a/packages/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 similarity index 100% rename from packages/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java rename to packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java diff --git a/packages/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 similarity index 100% rename from packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java rename to packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java diff --git a/packages/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 similarity index 100% rename from packages/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java rename to packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java diff --git a/packages/camera/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java similarity index 100% rename from packages/camera/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java rename to packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java diff --git a/packages/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java similarity index 100% rename from packages/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java rename to packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java diff --git a/packages/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java similarity index 100% rename from packages/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java rename to packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java diff --git a/packages/camera/camera_android.iml b/packages/camera/camera/camera_android.iml similarity index 100% rename from packages/camera/camera_android.iml rename to packages/camera/camera/camera_android.iml diff --git a/packages/camera/example/android.iml b/packages/camera/camera/example/android.iml similarity index 100% rename from packages/camera/example/android.iml rename to packages/camera/camera/example/android.iml diff --git a/packages/camera/example/android/app/build.gradle b/packages/camera/camera/example/android/app/build.gradle similarity index 100% rename from packages/camera/example/android/app/build.gradle rename to packages/camera/camera/example/android/app/build.gradle diff --git a/packages/camera/example/android/app/gradle/wrapper/gradle-wrapper.properties b/packages/camera/camera/example/android/app/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/camera/example/android/app/gradle/wrapper/gradle-wrapper.properties rename to packages/camera/camera/example/android/app/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/EmbeddingV1ActivityTest.java b/packages/camera/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/EmbeddingV1ActivityTest.java similarity index 100% rename from packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/EmbeddingV1ActivityTest.java rename to packages/camera/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/EmbeddingV1ActivityTest.java diff --git a/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/FlutterActivityTest.java b/packages/camera/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/FlutterActivityTest.java similarity index 100% rename from packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/FlutterActivityTest.java rename to packages/camera/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/FlutterActivityTest.java diff --git a/packages/camera/example/android/app/src/main/AndroidManifest.xml b/packages/camera/camera/example/android/app/src/main/AndroidManifest.xml similarity index 100% rename from packages/camera/example/android/app/src/main/AndroidManifest.xml rename to packages/camera/camera/example/android/app/src/main/AndroidManifest.xml diff --git a/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/EmbeddingV1Activity.java b/packages/camera/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/EmbeddingV1Activity.java similarity index 100% rename from packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/EmbeddingV1Activity.java rename to packages/camera/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/EmbeddingV1Activity.java diff --git a/packages/camera/example/android/app/src/main/res/drawable/launch_background.xml b/packages/camera/camera/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from packages/camera/example/android/app/src/main/res/drawable/launch_background.xml rename to packages/camera/camera/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/packages/camera/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/camera/camera/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/camera/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/camera/camera/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/camera/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/camera/camera/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/camera/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/camera/camera/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/camera/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/camera/camera/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/camera/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/camera/camera/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/camera/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/camera/camera/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/camera/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/camera/camera/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/camera/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/camera/camera/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/camera/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/camera/camera/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/camera/example/android/app/src/main/res/values/styles.xml b/packages/camera/camera/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from packages/camera/example/android/app/src/main/res/values/styles.xml rename to packages/camera/camera/example/android/app/src/main/res/values/styles.xml diff --git a/packages/camera/example/android/build.gradle b/packages/camera/camera/example/android/build.gradle similarity index 100% rename from packages/camera/example/android/build.gradle rename to packages/camera/camera/example/android/build.gradle diff --git a/packages/camera/example/android/gradle.properties b/packages/camera/camera/example/android/gradle.properties similarity index 100% rename from packages/camera/example/android/gradle.properties rename to packages/camera/camera/example/android/gradle.properties diff --git a/packages/camera/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/camera/camera/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/camera/example/android/gradle/wrapper/gradle-wrapper.properties rename to packages/camera/camera/example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/camera/example/android/settings.gradle b/packages/camera/camera/example/android/settings.gradle similarity index 100% rename from packages/camera/example/android/settings.gradle rename to packages/camera/camera/example/android/settings.gradle diff --git a/packages/camera/example/camera_example.iml b/packages/camera/camera/example/camera_example.iml similarity index 100% rename from packages/camera/example/camera_example.iml rename to packages/camera/camera/example/camera_example.iml diff --git a/packages/camera/example/camera_example_android.iml b/packages/camera/camera/example/camera_example_android.iml similarity index 100% rename from packages/camera/example/camera_example_android.iml rename to packages/camera/camera/example/camera_example_android.iml diff --git a/packages/camera/example/integration_test/camera_test.dart b/packages/camera/camera/example/integration_test/camera_test.dart similarity index 100% rename from packages/camera/example/integration_test/camera_test.dart rename to packages/camera/camera/example/integration_test/camera_test.dart diff --git a/packages/camera/example/ios/Flutter/AppFrameworkInfo.plist b/packages/camera/camera/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from packages/camera/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/camera/camera/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/packages/camera/example/ios/Flutter/Debug.xcconfig b/packages/camera/camera/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from packages/camera/example/ios/Flutter/Debug.xcconfig rename to packages/camera/camera/example/ios/Flutter/Debug.xcconfig diff --git a/packages/camera/example/ios/Flutter/Release.xcconfig b/packages/camera/camera/example/ios/Flutter/Release.xcconfig similarity index 100% rename from packages/camera/example/ios/Flutter/Release.xcconfig rename to packages/camera/camera/example/ios/Flutter/Release.xcconfig diff --git a/packages/camera/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from packages/camera/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj diff --git a/packages/camera/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/camera/camera/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/camera/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/camera/camera/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/camera/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/camera/camera/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/camera/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/camera/camera/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/camera/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/camera/camera/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/camera/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/camera/camera/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/camera/example/ios/Runner/AppDelegate.h b/packages/camera/camera/example/ios/Runner/AppDelegate.h similarity index 100% rename from packages/camera/example/ios/Runner/AppDelegate.h rename to packages/camera/camera/example/ios/Runner/AppDelegate.h diff --git a/packages/camera/example/ios/Runner/AppDelegate.m b/packages/camera/camera/example/ios/Runner/AppDelegate.m similarity index 100% rename from packages/camera/example/ios/Runner/AppDelegate.m rename to packages/camera/camera/example/ios/Runner/AppDelegate.m diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/packages/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from packages/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/packages/camera/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/camera/camera/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from packages/camera/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/camera/camera/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/packages/camera/example/ios/Runner/Base.lproj/Main.storyboard b/packages/camera/camera/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/camera/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/camera/camera/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/camera/example/ios/Runner/Info.plist b/packages/camera/camera/example/ios/Runner/Info.plist similarity index 100% rename from packages/camera/example/ios/Runner/Info.plist rename to packages/camera/camera/example/ios/Runner/Info.plist diff --git a/packages/camera/example/ios/Runner/main.m b/packages/camera/camera/example/ios/Runner/main.m similarity index 100% rename from packages/camera/example/ios/Runner/main.m rename to packages/camera/camera/example/ios/Runner/main.m diff --git a/packages/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart similarity index 100% rename from packages/camera/example/lib/main.dart rename to packages/camera/camera/example/lib/main.dart diff --git a/packages/camera/example/pubspec.yaml b/packages/camera/camera/example/pubspec.yaml similarity index 100% rename from packages/camera/example/pubspec.yaml rename to packages/camera/camera/example/pubspec.yaml diff --git a/packages/camera/example/test_driver/integration_test.dart b/packages/camera/camera/example/test_driver/integration_test.dart similarity index 100% rename from packages/camera/example/test_driver/integration_test.dart rename to packages/camera/camera/example/test_driver/integration_test.dart diff --git a/packages/camera/ios/Assets/.gitkeep b/packages/camera/camera/ios/Assets/.gitkeep similarity index 100% rename from packages/camera/ios/Assets/.gitkeep rename to packages/camera/camera/ios/Assets/.gitkeep diff --git a/packages/camera/ios/Classes/CameraPlugin.h b/packages/camera/camera/ios/Classes/CameraPlugin.h similarity index 100% rename from packages/camera/ios/Classes/CameraPlugin.h rename to packages/camera/camera/ios/Classes/CameraPlugin.h diff --git a/packages/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m similarity index 100% rename from packages/camera/ios/Classes/CameraPlugin.m rename to packages/camera/camera/ios/Classes/CameraPlugin.m diff --git a/packages/camera/ios/Tests/CameraPluginTests.m b/packages/camera/camera/ios/Tests/CameraPluginTests.m similarity index 100% rename from packages/camera/ios/Tests/CameraPluginTests.m rename to packages/camera/camera/ios/Tests/CameraPluginTests.m diff --git a/packages/camera/ios/camera.podspec b/packages/camera/camera/ios/camera.podspec similarity index 100% rename from packages/camera/ios/camera.podspec rename to packages/camera/camera/ios/camera.podspec diff --git a/packages/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart similarity index 100% rename from packages/camera/lib/camera.dart rename to packages/camera/camera/lib/camera.dart diff --git a/packages/camera/lib/camera_image.dart b/packages/camera/camera/lib/camera_image.dart similarity index 100% rename from packages/camera/lib/camera_image.dart rename to packages/camera/camera/lib/camera_image.dart diff --git a/packages/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml similarity index 100% rename from packages/camera/pubspec.yaml rename to packages/camera/camera/pubspec.yaml From 8a361ba7b2d224028958f5093c0e2e4a09d16ae6 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 9 Nov 2020 08:47:52 +0100 Subject: [PATCH 02/89] Fix relative path after move --- packages/camera/camera/example/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera/example/pubspec.yaml b/packages/camera/camera/example/pubspec.yaml index bc33087a5877..0d1f03bef437 100644 --- a/packages/camera/camera/example/pubspec.yaml +++ b/packages/camera/camera/example/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: sdk: flutter video_player: ^0.10.0 integration_test: - path: ../../integration_test + path: ../../../integration_test dev_dependencies: flutter_test: From 0662dc4623ce19c5072deb92773c62deebcfab5b Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 6 Nov 2020 16:36:33 +0100 Subject: [PATCH 03/89] First suggestion for camera platform interface --- .../camera_platform_interface/CHANGELOG.md | 3 + .../camera/camera_platform_interface/LICENSE | 25 + .../camera_platform_interface/README.md | 26 + .../coverage/html/amber.png | Bin 0 -> 141 bytes .../coverage/html/emerald.png | Bin 0 -> 141 bytes .../coverage/html/gcov.css | 519 ++++++++++++++++++ .../coverage/html/glass.png | Bin 0 -> 167 bytes .../coverage/html/index-sort-f.html | 103 ++++ .../coverage/html/index-sort-l.html | 103 ++++ .../coverage/html/index.html | 103 ++++ .../camera_platform.dart.func-sort-c.html | 72 +++ .../camera_platform.dart.func.html | 72 +++ .../camera_platform.dart.gcov.html | 191 +++++++ .../html/platform_interface/index-sort-f.html | 93 ++++ .../html/platform_interface/index-sort-l.html | 93 ++++ .../html/platform_interface/index.html | 93 ++++ .../coverage/html/ruby.png | Bin 0 -> 141 bytes .../coverage/html/snow.png | Bin 0 -> 141 bytes .../camera_description.dart.func-sort-c.html | 72 +++ .../types/camera_description.dart.func.html | 72 +++ .../types/camera_description.dart.gcov.html | 131 +++++ .../types/camera_event.dart.func-sort-c.html | 72 +++ .../html/types/camera_event.dart.func.html | 72 +++ .../html/types/camera_event.dart.gcov.html | 152 +++++ .../types/camera_image.dart.func-sort-c.html | 72 +++ .../html/types/camera_image.dart.func.html | 72 +++ .../html/types/camera_image.dart.gcov.html | 224 ++++++++ .../coverage/html/types/index-sort-f.html | 123 +++++ .../coverage/html/types/index-sort-l.html | 123 +++++ .../coverage/html/types/index.html | 123 +++++ .../resolution_preset.dart.func-sort-c.html | 72 +++ .../types/resolution_preset.dart.func.html | 72 +++ .../types/resolution_preset.dart.gcov.html | 98 ++++ .../coverage/html/updown.png | Bin 0 -> 117 bytes .../coverage/lcov.info | 110 ++++ .../lib/camera_platform_interface.dart | 6 + .../method_channel/method_channel_camera.dart | 12 + .../platform_interface/camera_platform.dart | 115 ++++ .../lib/src/types/callbacks.dart | 11 + .../lib/src/types/camera_description.dart | 55 ++ .../lib/src/types/camera_event.dart | 76 +++ .../lib/src/types/camera_image.dart | 148 +++++ .../lib/src/types/resolution_preset.dart | 22 + .../lib/src/types/types.dart | 9 + .../camera_platform_interface/pubspec.yaml | 22 + .../test/camera_platform_interface_test.dart | 205 +++++++ .../test/types/camera_image_test.dart | 113 ++++ 47 files changed, 3950 insertions(+) create mode 100644 packages/camera/camera_platform_interface/CHANGELOG.md create mode 100644 packages/camera/camera_platform_interface/LICENSE create mode 100644 packages/camera/camera_platform_interface/README.md create mode 100644 packages/camera/camera_platform_interface/coverage/html/amber.png create mode 100644 packages/camera/camera_platform_interface/coverage/html/emerald.png create mode 100644 packages/camera/camera_platform_interface/coverage/html/gcov.css create mode 100644 packages/camera/camera_platform_interface/coverage/html/glass.png create mode 100644 packages/camera/camera_platform_interface/coverage/html/index-sort-f.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/index-sort-l.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/index.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/ruby.png create mode 100644 packages/camera/camera_platform_interface/coverage/html/snow.png create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/index.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html create mode 100644 packages/camera/camera_platform_interface/coverage/html/updown.png create mode 100644 packages/camera/camera_platform_interface/coverage/lcov.info create mode 100644 packages/camera/camera_platform_interface/lib/camera_platform_interface.dart create mode 100644 packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart create mode 100644 packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart create mode 100644 packages/camera/camera_platform_interface/lib/src/types/callbacks.dart create mode 100644 packages/camera/camera_platform_interface/lib/src/types/camera_description.dart create mode 100644 packages/camera/camera_platform_interface/lib/src/types/camera_event.dart create mode 100644 packages/camera/camera_platform_interface/lib/src/types/camera_image.dart create mode 100644 packages/camera/camera_platform_interface/lib/src/types/resolution_preset.dart create mode 100644 packages/camera/camera_platform_interface/lib/src/types/types.dart create mode 100644 packages/camera/camera_platform_interface/pubspec.yaml create mode 100644 packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart create mode 100644 packages/camera/camera_platform_interface/test/types/camera_image_test.dart diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md new file mode 100644 index 000000000000..6cf2f5bd17dd --- /dev/null +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +- Initial open-source release diff --git a/packages/camera/camera_platform_interface/LICENSE b/packages/camera/camera_platform_interface/LICENSE new file mode 100644 index 000000000000..a6d6c0749818 --- /dev/null +++ b/packages/camera/camera_platform_interface/LICENSE @@ -0,0 +1,25 @@ +Copyright 2017 The Chromium Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/camera/camera_platform_interface/README.md b/packages/camera/camera_platform_interface/README.md new file mode 100644 index 000000000000..43be651935b5 --- /dev/null +++ b/packages/camera/camera_platform_interface/README.md @@ -0,0 +1,26 @@ +# camera_platform_interface + +A common platform interface for the [`camera`][1] plugin. + +This interface allows platform-specific implementations of the `camera` +plugin, as well as the plugin itself, to ensure they are supporting the +same interface. + +# Usage + +To implement a new platform-specific implementation of `camera`, extend +[`CameraPlatform`][2] with an implementation that performs the +platform-specific behavior, and when you register your plugin, set the default +`CameraPlatform` by calling +`CameraPlatform.instance = MyPlatformCamera()`. + +# Note on breaking changes + +Strongly prefer non-breaking changes (such as adding a method to the interface) +over breaking changes for this package. + +See https://flutter.dev/go/platform-interface-breaking-changes for a discussion +on why a less-clean interface is preferable to a breaking change. + +[1]: ../camera +[2]: lib/camera_platform_interface.dart diff --git a/packages/camera/camera_platform_interface/coverage/html/amber.png b/packages/camera/camera_platform_interface/coverage/html/amber.png new file mode 100644 index 0000000000000000000000000000000000000000..2cab170d8359081983a4e343848dfe06bc490f12 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^G2tW}LqE04T&+ z;1OBOz`!j8!i<;h*8KqrvZOouIx;Y9?C1WI$O`1M1^9%x{(levWG?NMQuI!iC1^Jb!lvI6;R0X`wF(yt=9xVZRt1vCRixIA4P dLn>}1Cji+@42)0J?}79&c)I$ztaD0e0sy@GAL0N2 literal 0 HcmV?d00001 diff --git a/packages/camera/camera_platform_interface/coverage/html/gcov.css b/packages/camera/camera_platform_interface/coverage/html/gcov.css new file mode 100644 index 000000000000..bfd0a83e10b5 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/gcov.css @@ -0,0 +1,519 @@ +/* All views: initial background and text color */ +body +{ + color: #000000; + background-color: #FFFFFF; +} + +/* All views: standard link format*/ +a:link +{ + color: #284FA8; + text-decoration: underline; +} + +/* All views: standard link - visited format */ +a:visited +{ + color: #00CB40; + text-decoration: underline; +} + +/* All views: standard link - activated format */ +a:active +{ + color: #FF0040; + text-decoration: underline; +} + +/* All views: main title format */ +td.title +{ + text-align: center; + padding-bottom: 10px; + font-family: sans-serif; + font-size: 20pt; + font-style: italic; + font-weight: bold; +} + +/* All views: header item format */ +td.headerItem +{ + text-align: right; + padding-right: 6px; + font-family: sans-serif; + font-weight: bold; + vertical-align: top; + white-space: nowrap; +} + +/* All views: header item value format */ +td.headerValue +{ + text-align: left; + color: #284FA8; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; +} + +/* All views: header item coverage table heading */ +td.headerCovTableHead +{ + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + font-size: 80%; + white-space: nowrap; +} + +/* All views: header item coverage table entry */ +td.headerCovTableEntry +{ + text-align: right; + color: #284FA8; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #DAE7FE; +} + +/* All views: header item coverage table entry for high coverage rate */ +td.headerCovTableEntryHi +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #A7FC9D; +} + +/* All views: header item coverage table entry for medium coverage rate */ +td.headerCovTableEntryMed +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #FFEA20; +} + +/* All views: header item coverage table entry for ow coverage rate */ +td.headerCovTableEntryLo +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #FF0000; +} + +/* All views: header legend value for legend entry */ +td.headerValueLeg +{ + text-align: left; + color: #000000; + font-family: sans-serif; + font-size: 80%; + white-space: nowrap; + padding-top: 4px; +} + +/* All views: color of horizontal ruler */ +td.ruler +{ + background-color: #6688D4; +} + +/* All views: version string format */ +td.versionInfo +{ + text-align: center; + padding-top: 2px; + font-family: sans-serif; + font-style: italic; +} + +/* Directory view/File view (all)/Test case descriptions: + table headline format */ +td.tableHead +{ + text-align: center; + color: #FFFFFF; + background-color: #6688D4; + font-family: sans-serif; + font-size: 120%; + font-weight: bold; + white-space: nowrap; + padding-left: 4px; + padding-right: 4px; +} + +span.tableHeadSort +{ + padding-right: 4px; +} + +/* Directory view/File view (all): filename entry format */ +td.coverFile +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284FA8; + background-color: #DAE7FE; + font-family: monospace; +} + +/* Directory view/File view (all): bar-graph entry format*/ +td.coverBar +{ + padding-left: 10px; + padding-right: 10px; + background-color: #DAE7FE; +} + +/* Directory view/File view (all): bar-graph outline color */ +td.coverBarOutline +{ + background-color: #000000; +} + +/* Directory view/File view (all): percentage entry for files with + high coverage rate */ +td.coverPerHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #A7FC9D; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry for files with + high coverage rate */ +td.coverNumHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #A7FC9D; + white-space: nowrap; + font-family: sans-serif; +} + +/* Directory view/File view (all): percentage entry for files with + medium coverage rate */ +td.coverPerMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FFEA20; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry for files with + medium coverage rate */ +td.coverNumMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FFEA20; + white-space: nowrap; + font-family: sans-serif; +} + +/* Directory view/File view (all): percentage entry for files with + low coverage rate */ +td.coverPerLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FF0000; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry for files with + low coverage rate */ +td.coverNumLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FF0000; + white-space: nowrap; + font-family: sans-serif; +} + +/* File view (all): "show/hide details" link format */ +a.detail:link +{ + color: #B8D0FF; + font-size:80%; +} + +/* File view (all): "show/hide details" link - visited format */ +a.detail:visited +{ + color: #B8D0FF; + font-size:80%; +} + +/* File view (all): "show/hide details" link - activated format */ +a.detail:active +{ + color: #FFFFFF; + font-size:80%; +} + +/* File view (detail): test name entry */ +td.testName +{ + text-align: right; + padding-right: 10px; + background-color: #DAE7FE; + font-family: sans-serif; +} + +/* File view (detail): test percentage entry */ +td.testPer +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #DAE7FE; + font-family: sans-serif; +} + +/* File view (detail): test lines count entry */ +td.testNum +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #DAE7FE; + font-family: sans-serif; +} + +/* Test case descriptions: test name format*/ +dt +{ + font-family: sans-serif; + font-weight: bold; +} + +/* Test case descriptions: description table body */ +td.testDescription +{ + padding-top: 10px; + padding-left: 30px; + padding-bottom: 10px; + padding-right: 30px; + background-color: #DAE7FE; +} + +/* Source code view: function entry */ +td.coverFn +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284FA8; + background-color: #DAE7FE; + font-family: monospace; +} + +/* Source code view: function entry zero count*/ +td.coverFnLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FF0000; + font-weight: bold; + font-family: sans-serif; +} + +/* Source code view: function entry nonzero count*/ +td.coverFnHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #DAE7FE; + font-weight: bold; + font-family: sans-serif; +} + +/* Source code view: source code format */ +pre.source +{ + font-family: monospace; + white-space: pre; + margin-top: 2px; +} + +/* Source code view: line number format */ +span.lineNum +{ + background-color: #EFE383; +} + +/* Source code view: format for lines which were executed */ +td.lineCov, +span.lineCov +{ + background-color: #CAD7FE; +} + +/* Source code view: format for Cov legend */ +span.coverLegendCov +{ + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + background-color: #CAD7FE; +} + +/* Source code view: format for lines which were not executed */ +td.lineNoCov, +span.lineNoCov +{ + background-color: #FF6230; +} + +/* Source code view: format for NoCov legend */ +span.coverLegendNoCov +{ + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + background-color: #FF6230; +} + +/* Source code view (function table): standard link - visited format */ +td.lineNoCov > a:visited, +td.lineCov > a:visited +{ + color: black; + text-decoration: underline; +} + +/* Source code view: format for lines which were executed only in a + previous version */ +span.lineDiffCov +{ + background-color: #B5F7AF; +} + +/* Source code view: format for branches which were executed + * and taken */ +span.branchCov +{ + background-color: #CAD7FE; +} + +/* Source code view: format for branches which were executed + * but not taken */ +span.branchNoCov +{ + background-color: #FF6230; +} + +/* Source code view: format for branches which were not executed */ +span.branchNoExec +{ + background-color: #FF6230; +} + +/* Source code view: format for the source code heading line */ +pre.sourceHeading +{ + white-space: pre; + font-family: monospace; + font-weight: bold; + margin: 0px; +} + +/* All views: header legend value for low rate */ +td.headerValueLegL +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 4px; + padding-right: 2px; + background-color: #FF0000; + font-size: 80%; +} + +/* All views: header legend value for med rate */ +td.headerValueLegM +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 2px; + background-color: #FFEA20; + font-size: 80%; +} + +/* All views: header legend value for hi rate */ +td.headerValueLegH +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 4px; + background-color: #A7FC9D; + font-size: 80%; +} + +/* All views except source code view: legend format for low coverage */ +span.coverLegendCovLo +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #FF0000; +} + +/* All views except source code view: legend format for med coverage */ +span.coverLegendCovMed +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #FFEA20; +} + +/* All views except source code view: legend format for hi coverage */ +span.coverLegendCovHi +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #A7FC9D; +} diff --git a/packages/camera/camera_platform_interface/coverage/html/glass.png b/packages/camera/camera_platform_interface/coverage/html/glass.png new file mode 100644 index 0000000000000000000000000000000000000000..e1abc00680a3093c49fdb775ae6bdb6764c95af2 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)gaEa{HEjtmSN`?>!lvI6;R0X`wF z|Ns97GD8ntt^-nxB|(0{3=Yq3q=7g|-tI089jvk*Kn`btM`SSr1Gf+eGhVt|_XjA* zUgGKN%6^Gmn4d%Ph(nkFP>9RZ#WAE}PI3Z}&BVayv3^M*kj3EX>gTe~DWM4f=_Dpv literal 0 HcmV?d00001 diff --git a/packages/camera/camera_platform_interface/coverage/html/index-sort-f.html b/packages/camera/camera_platform_interface/coverage/html/index-sort-f.html new file mode 100644 index 000000000000..6615ccc56576 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/index-sort-f.html @@ -0,0 +1,103 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelHitTotalCoverage
Test:lcov.infoLines:689075.6 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
platform_interface +
100.0%
+
100.0 %30 / 30-0 / 0
types +
63.3%63.3%
+
63.3 %38 / 60-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/index-sort-l.html b/packages/camera/camera_platform_interface/coverage/html/index-sort-l.html new file mode 100644 index 000000000000..738b57014f22 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/index-sort-l.html @@ -0,0 +1,103 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelHitTotalCoverage
Test:lcov.infoLines:689075.6 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
types +
63.3%63.3%
+
63.3 %38 / 60-0 / 0
platform_interface +
100.0%
+
100.0 %30 / 30-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/index.html b/packages/camera/camera_platform_interface/coverage/html/index.html new file mode 100644 index 000000000000..4a459d170aa7 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/index.html @@ -0,0 +1,103 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelHitTotalCoverage
Test:lcov.infoLines:689075.6 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
platform_interface +
100.0%
+
100.0 %30 / 30-0 / 0
types +
63.3%63.3%
+
63.3 %38 / 60-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html new file mode 100644 index 000000000000..1ed65bcc45af --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html @@ -0,0 +1,72 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - platform_interface - camera_platform.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html new file mode 100644 index 000000000000..eadf62bde488 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html @@ -0,0 +1,72 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - platform_interface - camera_platform.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html new file mode 100644 index 000000000000..b667827592e8 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html @@ -0,0 +1,191 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - platform_interface - camera_platform.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : // Copyright 2018 The Chromium Authors. All rights reserved.
+       2             : // Use of this source code is governed by a BSD-style license that can be
+       3             : // found in the LICENSE file.
+       4             : 
+       5             : import 'dart:async';
+       6             : 
+       7             : import 'package:plugin_platform_interface/plugin_platform_interface.dart';
+       8             : import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart';
+       9             : 
+      10             : import '../../camera_platform_interface.dart';
+      11             : 
+      12             : /// The interface that implementations of camera must implement.
+      13             : ///
+      14             : /// Platform implementations should extend this class rather than implement it as `camera`
+      15             : /// does not consider newly added methods to be breaking changes. Extending this class
+      16             : /// (using `extends`) ensures that the subclass will get the default implementation, while
+      17             : /// platform implementations that `implements` this interface will be broken by newly added
+      18             : /// [CameraPlatform] methods.
+      19             : abstract class CameraPlatform extends PlatformInterface {
+      20             :   /// Constructs a CameraPlatform.
+      21           3 :   CameraPlatform() : super(token: _token);
+      22             : 
+      23           3 :   static final Object _token = Object();
+      24             : 
+      25           3 :   static CameraPlatform _instance = MethodChannelCamera();
+      26             : 
+      27             :   /// The default instance of [CameraPlatform] to use.
+      28             :   ///
+      29             :   /// Defaults to [MethodChannelCamera].
+      30           2 :   static CameraPlatform get instance => _instance;
+      31             : 
+      32             :   /// Platform-specific plugins should set this with their own platform-specific
+      33             :   /// class that extends [CameraPlatform] when they register themselves.
+      34           1 :   static set instance(CameraPlatform instance) {
+      35           2 :     PlatformInterface.verifyToken(instance, _token);
+      36             :     _instance = instance;
+      37             :   }
+      38             : 
+      39             :   /// Completes with a list of available cameras.
+      40           1 :   Future<List<CameraDescription>> availableCameras() {
+      41           1 :     throw UnimplementedError('availableCameras() is not implemented.');
+      42             :   }
+      43             : 
+      44             :   /// Initializes the camera on the device and returns its textureId.
+      45           1 :   Future<int> initializeCamera(
+      46             :     CameraDescription cameraDescription, {
+      47             :     ResolutionPreset resolutionPreset,
+      48             :     bool enableAudio,
+      49             :   }) {
+      50           1 :     throw UnimplementedError('initializeCamera() is not implemented.');
+      51             :   }
+      52             : 
+      53             :   /// Returns a Stream of [CameraEvent]s.
+      54           1 :   Stream<CameraEvent> cameraEventsFor(int textureId) {
+      55           1 :     throw UnimplementedError('videoEventsFor() has not been implemented.');
+      56             :   }
+      57             : 
+      58             :   /// Captures an image and saves it to [path].
+      59           1 :   Future<void> takePicture(int textureId, String path) {
+      60           1 :     throw UnimplementedError('takePicture() is not implemented.');
+      61             :   }
+      62             : 
+      63             :   /// Prepare the capture session for video recording.
+      64           1 :   Future<void> prepareForVideoRecording() {
+      65           1 :     throw UnimplementedError('prepareForVideoRecording() is not implemented.');
+      66             :   }
+      67             : 
+      68             :   /// Start a video recording and save the file to [path].
+      69             :   ///
+      70             :   /// A path can for example be obtained using
+      71             :   /// [path_provider](https://pub.dartlang.org/packages/path_provider).
+      72             :   ///
+      73             :   /// The file is written on the flight as the video is being recorded.
+      74             :   /// If a file already exists at the provided path an error will be thrown.
+      75             :   /// The file can be read as soon as [stopVideoRecording] returns.
+      76           1 :   Future<void> startVideoRecording(int textureId, String path) {
+      77           1 :     throw UnimplementedError('startVideoRecording() is not implemented.');
+      78             :   }
+      79             : 
+      80             :   /// Stop the video recording.
+      81           1 :   Future<void> stopVideoRecording(int textureId) {
+      82           1 :     throw UnimplementedError('stopVideoRecording() is not implemented.');
+      83             :   }
+      84             : 
+      85             :   /// Pause video recording.
+      86           1 :   Future<void> pauseVideoRecording(int textureId) {
+      87           1 :     throw UnimplementedError('pauseVideoRecording() is not implemented.');
+      88             :   }
+      89             : 
+      90             :   /// Resume video recording after pausing.
+      91           1 :   Future<void> resumeVideoRecording(int textureId) {
+      92           1 :     throw UnimplementedError('resumeVideoRecording() is not implemented.');
+      93             :   }
+      94             : 
+      95             :   /// Start streaming images from platform camera.
+      96             :   ///
+      97             :   /// When running continuously with [CameraPreview] widget, this function runs
+      98             :   /// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can
+      99             :   /// have significant frame rate drops for [CameraPreview] on lower end
+     100             :   /// devices.
+     101             :   // TODO(bmparr): Add settings for resolution and fps.
+     102           1 :   Future<void> startImageStream(onLatestImageAvailable onAvailable) {
+     103           1 :     throw UnimplementedError('startImageStream() is not implemented.');
+     104             :   }
+     105             : 
+     106             :   /// Stop streaming images from platform camera.
+     107           1 :   Future<void> stopImageStream() {
+     108           1 :     throw UnimplementedError('stopImageStream() is not implemented.');
+     109             :   }
+     110             : 
+     111             :   /// Releases the resources of this camera.
+     112           1 :   Future<void> dispose(int textureId) {
+     113           1 :     throw UnimplementedError('dispose() is not implemented.');
+     114             :   }
+     115             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html new file mode 100644 index 000000000000..58bbd9987ed2 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html @@ -0,0 +1,93 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - platform_interfaceHitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_platform.dart +
100.0%
+
100.0 %30 / 30-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html new file mode 100644 index 000000000000..d4f9ff9f5ca9 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html @@ -0,0 +1,93 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - platform_interfaceHitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_platform.dart +
100.0%
+
100.0 %30 / 30-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html new file mode 100644 index 000000000000..413082c13942 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html @@ -0,0 +1,93 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - platform_interfaceHitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_platform.dart +
100.0%
+
100.0 %30 / 30-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/ruby.png b/packages/camera/camera_platform_interface/coverage/html/ruby.png new file mode 100644 index 0000000000000000000000000000000000000000..991b6d4ec9e78be165e3ef757eed1aada287364d GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^FceV#7`HfI^%F z9+AZi4BSE>%y{W;-5;PJOS+@4BLl<6e(pbstUx|nfKQ0)e^Y%R^MdiLxj>4`)5S5Q b;#P73kj=!v_*DHKNFRfztDnm{r-UW|iOwIS literal 0 HcmV?d00001 diff --git a/packages/camera/camera_platform_interface/coverage/html/snow.png b/packages/camera/camera_platform_interface/coverage/html/snow.png new file mode 100644 index 0000000000000000000000000000000000000000..2cdae107fceec6e7f02ac7acb4a34a82a540caa5 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^MM!lvI6;R0X`wF|Ns97GD8ntt^-nBo-U3d c6}OTTfNUlP#;5A{K>8RwUHx3vIVCg!071?oo&W#< literal 0 HcmV?d00001 diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html new file mode 100644 index 000000000000..f9ec1768180f --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html @@ -0,0 +1,72 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_description.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41330.8 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html new file mode 100644 index 000000000000..4031fb3e2412 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html @@ -0,0 +1,72 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_description.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41330.8 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html new file mode 100644 index 000000000000..10699d953f8e --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html @@ -0,0 +1,131 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_description.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41330.8 %
Date:2020-11-06 16:26:41Functions:00-
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : // Copyright 2018 The Chromium Authors. All rights reserved.
+       2             : // Use of this source code is governed by a BSD-style license that can be
+       3             : // found in the LICENSE file.
+       4             : 
+       5             : import 'dart:ui';
+       6             : 
+       7             : /// The direction the camera is facing.
+       8           2 : enum CameraLensDirection {
+       9             :   /// Front facing camera (a user looking at the screen is seen by the camera).
+      10           2 :   front,
+      11             : 
+      12             :   /// Back facing camera (a user looking at the screen is not seen by the camera).
+      13           2 :   back,
+      14             : 
+      15             :   /// External camera which may not be mounted to the device.
+      16           2 :   external,
+      17             : }
+      18             : 
+      19             : /// Properties of a camera device.
+      20             : class CameraDescription {
+      21             :   /// Creates a new camera description with the given properties.
+      22           0 :   CameraDescription({this.name, this.lensDirection, this.sensorOrientation});
+      23             : 
+      24             :   /// The name of the camera device.
+      25             :   final String name;
+      26             : 
+      27             :   /// The direction the camera is facing.
+      28             :   final CameraLensDirection lensDirection;
+      29             : 
+      30             :   /// Clockwise angle through which the output image needs to be rotated to be upright on the device screen in its native orientation.
+      31             :   ///
+      32             :   /// **Range of valid values:**
+      33             :   /// 0, 90, 180, 270
+      34             :   ///
+      35             :   /// On Android, also defines the direction of rolling shutter readout, which
+      36             :   /// is from top to bottom in the sensor's coordinate system.
+      37             :   final int sensorOrientation;
+      38             : 
+      39           0 :   @override
+      40             :   bool operator ==(Object o) {
+      41           0 :     return o is CameraDescription &&
+      42           0 :         o.name == name &&
+      43           0 :         o.lensDirection == lensDirection;
+      44             :   }
+      45             : 
+      46           0 :   @override
+      47             :   int get hashCode {
+      48           0 :     return hashValues(name, lensDirection);
+      49             :   }
+      50             : 
+      51           0 :   @override
+      52             :   String toString() {
+      53           0 :     return '$runtimeType($name, $lensDirection, $sensorOrientation)';
+      54             :   }
+      55             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html new file mode 100644 index 000000000000..f3a43cd1a63b --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html @@ -0,0 +1,72 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_event.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41723.5 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html new file mode 100644 index 000000000000..f364a44cca63 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html @@ -0,0 +1,72 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_event.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41723.5 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html new file mode 100644 index 000000000000..6b9684af05ce --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html @@ -0,0 +1,152 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_event.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41723.5 %
Date:2020-11-06 16:26:41Functions:00-
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : // Copyright 2017 The Chromium Authors. All rights reserved.
+       2             : // Use of this source code is governed by a BSD-style license that can be
+       3             : // found in the LICENSE file.
+       4             : 
+       5             : import 'dart:ui';
+       6             : 
+       7             : import 'package:meta/meta.dart';
+       8             : 
+       9             : /// Event emitted from the platform implementation.
+      10             : class CameraEvent {
+      11             :   /// Creates an instance of [CameraEvent].
+      12             :   ///
+      13             :   /// The [eventType] argument is required.
+      14             :   ///
+      15             :   /// Depending on the [eventType], the [captureSize], [previewSize] and 
+      16             :   /// [errorDescription] arguments can be null.
+      17           0 :   CameraEvent({
+      18             :     @required this.eventType,
+      19             :     this.captureSize,
+      20             :     this.previewSize,
+      21             :     this.errorDescription,
+      22             :   });
+      23             : 
+      24             :   /// The type of the event.
+      25             :   final CameraEventType eventType;
+      26             : 
+      27             :   /// The capture size in pixels.
+      28             :   /// 
+      29             :   /// Only used if the [eventType] is [CameraEventType.initialized]
+      30             :   final Size captureSize;
+      31             : 
+      32             :   /// The size of the preview in pixels.
+      33             :   /// 
+      34             :   /// Only used if the [eventType] is [CameraEventType.initialized]
+      35             :   final Size previewSize;
+      36             : 
+      37             :   /// Description of the error.
+      38             :   ///
+      39             :   /// Only used if [eventType] is [CameraEventType.error].
+      40             :   final String errorDescription;
+      41             : 
+      42           0 :   @override
+      43             :   bool operator ==(Object other) {
+      44             :     return identical(this, other) ||
+      45           0 :         other is CameraEvent &&
+      46           0 :             runtimeType == other.runtimeType &&
+      47           0 :             eventType == other.eventType &&
+      48           0 :             captureSize == other.captureSize &&
+      49           0 :             previewSize == other.previewSize &&
+      50           0 :             errorDescription == other.errorDescription;
+      51             :   }
+      52             : 
+      53           0 :   @override
+      54             :   int get hashCode =>
+      55           0 :       eventType.hashCode ^
+      56           0 :       captureSize.hashCode ^
+      57           0 :       previewSize.hashCode ^
+      58           0 :       errorDescription.hashCode;
+      59             : }
+      60             : 
+      61             : /// Type of the event.
+      62             : ///
+      63             : /// Emitted by the platform implementation when the camera is closing or
+      64             : /// if an error occured.
+      65           2 : enum CameraEventType {
+      66             :   /// The camera has been initialized.
+      67             : 
+      68             :   /// The camera is closing.
+      69           2 :   cameraClosing,
+      70             : 
+      71             :   /// An error occured while accessing the camera.
+      72           2 :   error,
+      73             : 
+      74             :   /// An unknown event has been received.
+      75           2 :   unknown,
+      76             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html new file mode 100644 index 000000000000..5cf21835a9fc --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html @@ -0,0 +1,72 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_image.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:2323100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html new file mode 100644 index 000000000000..945e78a95d84 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html @@ -0,0 +1,72 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_image.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:2323100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html new file mode 100644 index 000000000000..96ab1f424ec4 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html @@ -0,0 +1,224 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - camera_image.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:2323100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : // Copyright 2018 The Chromium Authors. All rights reserved.
+       2             : // Use of this source code is governed by a BSD-style license that can be
+       3             : // found in the LICENSE file.
+       4             : 
+       5             : import 'dart:typed_data';
+       6             : 
+       7             : import 'package:flutter/foundation.dart';
+       8             : 
+       9             : /// A single color plane of image data.
+      10             : ///
+      11             : /// The number and meaning of the planes in an image are determined by the
+      12             : /// format of the Image.
+      13             : class Plane {
+      14           1 :   Plane._fromPlatformData(Map<dynamic, dynamic> data)
+      15           1 :       : bytes = data['bytes'],
+      16           1 :         bytesPerPixel = data['bytesPerPixel'],
+      17           1 :         bytesPerRow = data['bytesPerRow'],
+      18           1 :         height = data['height'],
+      19           1 :         width = data['width'];
+      20             : 
+      21             :   /// Bytes representing this plane.
+      22             :   final Uint8List bytes;
+      23             : 
+      24             :   /// The distance between adjacent pixel samples on Android, in bytes.
+      25             :   ///
+      26             :   /// Will be `null` on iOS.
+      27             :   final int bytesPerPixel;
+      28             : 
+      29             :   /// The row stride for this color plane, in bytes.
+      30             :   final int bytesPerRow;
+      31             : 
+      32             :   /// Height of the pixel buffer on iOS.
+      33             :   ///
+      34             :   /// Will be `null` on Android
+      35             :   final int height;
+      36             : 
+      37             :   /// Width of the pixel buffer on iOS.
+      38             :   ///
+      39             :   /// Will be `null` on Android.
+      40             :   final int width;
+      41             : }
+      42             : 
+      43             : // TODO:(bmparr) Turn [ImageFormatGroup] to a class with int values.
+      44             : /// Group of image formats that are comparable across Android and iOS platforms.
+      45           2 : enum ImageFormatGroup {
+      46             :   /// The image format does not fit into any specific group.
+      47           2 :   unknown,
+      48             : 
+      49             :   /// Multi-plane YUV 420 format.
+      50             :   ///
+      51             :   /// This format is a generic YCbCr format, capable of describing any 4:2:0
+      52             :   /// chroma-subsampled planar or semiplanar buffer (but not fully interleaved),
+      53             :   /// with 8 bits per color sample.
+      54             :   ///
+      55             :   /// On Android, this is `android.graphics.ImageFormat.YUV_420_888`. See
+      56             :   /// https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888
+      57             :   ///
+      58             :   /// On iOS, this is `kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange`. See
+      59             :   /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_420ypcbcr8biplanarvideorange?language=objc
+      60           2 :   yuv420,
+      61             : 
+      62             :   /// 32-bit BGRA.
+      63             :   ///
+      64             :   /// On iOS, this is `kCVPixelFormatType_32BGRA`. See
+      65             :   /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_32bgra?language=objc
+      66           2 :   bgra8888,
+      67             : }
+      68             : 
+      69             : /// Describes how pixels are represented in an image.
+      70             : class ImageFormat {
+      71           2 :   ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw);
+      72             : 
+      73             :   /// Describes the format group the raw image format falls into.
+      74             :   final ImageFormatGroup group;
+      75             : 
+      76             :   /// Raw version of the format from the Android or iOS platform.
+      77             :   ///
+      78             :   /// On Android, this is an `int` from class `android.graphics.ImageFormat`. See
+      79             :   /// https://developer.android.com/reference/android/graphics/ImageFormat
+      80             :   ///
+      81             :   /// On iOS, this is a `FourCharCode` constant from Pixel Format Identifiers.
+      82             :   /// See https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers?language=objc
+      83             :   final dynamic raw;
+      84             : }
+      85             : 
+      86           1 : ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) {
+      87           2 :   if (defaultTargetPlatform == TargetPlatform.android) {
+      88             :     // android.graphics.ImageFormat.YUV_420_888
+      89           1 :     if (rawFormat == 35) {
+      90             :       return ImageFormatGroup.yuv420;
+      91             :     }
+      92             :   }
+      93             : 
+      94           2 :   if (defaultTargetPlatform == TargetPlatform.iOS) {
+      95             :     switch (rawFormat) {
+      96             :       // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
+      97           1 :       case 875704438:
+      98             :         return ImageFormatGroup.yuv420;
+      99             :       // kCVPixelFormatType_32BGRA
+     100           1 :       case 1111970369:
+     101             :         return ImageFormatGroup.bgra8888;
+     102             :     }
+     103             :   }
+     104             : 
+     105             :   return ImageFormatGroup.unknown;
+     106             : }
+     107             : 
+     108             : /// A single complete image buffer from the platform camera.
+     109             : ///
+     110             : /// This class allows for direct application access to the pixel data of an
+     111             : /// Image through one or more [Uint8List]. Each buffer is encapsulated in a
+     112             : /// [Plane] that describes the layout of the pixel data in that plane. The
+     113             : /// [CameraImage] is not directly usable as a UI resource.
+     114             : ///
+     115             : /// Although not all image formats are planar on iOS, we treat 1-dimensional
+     116             : /// images as single planar images.
+     117             : class CameraImage {
+     118             :   /// Creates a new [CameraImage] from the data received from the platform.
+     119           1 :   CameraImage.fromPlatformData(Map<dynamic, dynamic> data)
+     120           2 :       : format = ImageFormat._fromPlatformData(data['format']),
+     121           1 :         height = data['height'],
+     122           1 :         width = data['width'],
+     123           2 :         planes = List<Plane>.unmodifiable(data['planes']
+     124           3 :             .map((dynamic planeData) => Plane._fromPlatformData(planeData)));
+     125             : 
+     126             :   /// Format of the image provided.
+     127             :   ///
+     128             :   /// Determines the number of planes needed to represent the image, and
+     129             :   /// the general layout of the pixel data in each [Uint8List].
+     130             :   final ImageFormat format;
+     131             : 
+     132             :   /// Height of the image in pixels.
+     133             :   ///
+     134             :   /// For formats where some color channels are subsampled, this is the height
+     135             :   /// of the largest-resolution plane.
+     136             :   final int height;
+     137             : 
+     138             :   /// Width of the image in pixels.
+     139             :   ///
+     140             :   /// For formats where some color channels are subsampled, this is the width
+     141             :   /// of the largest-resolution plane.
+     142             :   final int width;
+     143             : 
+     144             :   /// The pixels planes for this image.
+     145             :   ///
+     146             :   /// The number of planes is determined by the format of the image.
+     147             :   final List<Plane> planes;
+     148             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html b/packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html new file mode 100644 index 000000000000..5cf500911692 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html @@ -0,0 +1,123 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - typesHitTotalCoverage
Test:lcov.infoLines:386063.3 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
resolution_preset.dart +
100.0%
+
100.0 %7 / 7-0 / 0
camera_description.dart +
30.8%30.8%
+
30.8 %4 / 13-0 / 0
camera_event.dart +
23.5%23.5%
+
23.5 %4 / 17-0 / 0
camera_image.dart +
100.0%
+
100.0 %23 / 23-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html b/packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html new file mode 100644 index 000000000000..913571e880cb --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html @@ -0,0 +1,123 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - typesHitTotalCoverage
Test:lcov.infoLines:386063.3 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_event.dart +
23.5%23.5%
+
23.5 %4 / 17-0 / 0
camera_description.dart +
30.8%30.8%
+
30.8 %4 / 13-0 / 0
resolution_preset.dart +
100.0%
+
100.0 %7 / 7-0 / 0
camera_image.dart +
100.0%
+
100.0 %23 / 23-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/index.html b/packages/camera/camera_platform_interface/coverage/html/types/index.html new file mode 100644 index 000000000000..479a3edaa376 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/index.html @@ -0,0 +1,123 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - typesHitTotalCoverage
Test:lcov.infoLines:386063.3 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_description.dart +
30.8%30.8%
+
30.8 %4 / 13-0 / 0
camera_event.dart +
23.5%23.5%
+
23.5 %4 / 17-0 / 0
camera_image.dart +
100.0%
+
100.0 %23 / 23-0 / 0
resolution_preset.dart +
100.0%
+
100.0 %7 / 7-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html new file mode 100644 index 000000000000..fdf79712000a --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html @@ -0,0 +1,72 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - resolution_preset.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:77100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html new file mode 100644 index 000000000000..40ed9b1de628 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html @@ -0,0 +1,72 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - resolution_preset.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:77100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ +
+ + + + + + +

Function Name Sort by function nameHit count Sort by hit count
+
+
+ + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html new file mode 100644 index 000000000000..05c40ae95295 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html @@ -0,0 +1,98 @@ + + + + + + + Codestin Search App + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - types - resolution_preset.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:77100.0 %
Date:2020-11-06 16:26:41Functions:00-
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : /// Affect the quality of video recording and image capture:
+       2             : ///
+       3             : /// If a preset is not available on the camera being used a preset of lower quality will be selected automatically.
+       4           2 : enum ResolutionPreset {
+       5             :   /// 352x288 on iOS, 240p (320x240) on Android
+       6           2 :   low,
+       7             : 
+       8             :   /// 480p (640x480 on iOS, 720x480 on Android)
+       9           2 :   medium,
+      10             : 
+      11             :   /// 720p (1280x720)
+      12           2 :   high,
+      13             : 
+      14             :   /// 1080p (1920x1080)
+      15           2 :   veryHigh,
+      16             : 
+      17             :   /// 2160p (3840x2160)
+      18           2 :   ultraHigh,
+      19             : 
+      20             :   /// The highest resolution available.
+      21           2 :   max,
+      22             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.15
+
+ + + diff --git a/packages/camera/camera_platform_interface/coverage/html/updown.png b/packages/camera/camera_platform_interface/coverage/html/updown.png new file mode 100644 index 0000000000000000000000000000000000000000..aa56a238b3e6c435265250f9266cd1b8caba0f20 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^AT}Qd8;}%R+`Ae`*?77*hG?8mPH5^{)z4*}Q$iB}huR`+ literal 0 HcmV?d00001 diff --git a/packages/camera/camera_platform_interface/coverage/lcov.info b/packages/camera/camera_platform_interface/coverage/lcov.info new file mode 100644 index 000000000000..f8be39954cf2 --- /dev/null +++ b/packages/camera/camera_platform_interface/coverage/lcov.info @@ -0,0 +1,110 @@ +SF:lib/src/types/camera_image.dart +DA:14,1 +DA:15,1 +DA:16,1 +DA:17,1 +DA:18,1 +DA:19,1 +DA:45,2 +DA:47,2 +DA:60,2 +DA:66,2 +DA:71,2 +DA:86,1 +DA:87,2 +DA:89,1 +DA:94,2 +DA:97,1 +DA:100,1 +DA:119,1 +DA:120,2 +DA:121,1 +DA:122,1 +DA:123,2 +DA:124,3 +LF:23 +LH:23 +end_of_record +SF:lib/src/platform_interface/camera_platform.dart +DA:21,3 +DA:23,3 +DA:25,3 +DA:30,2 +DA:34,1 +DA:35,2 +DA:40,1 +DA:41,1 +DA:45,1 +DA:50,1 +DA:54,1 +DA:55,1 +DA:59,1 +DA:60,1 +DA:64,1 +DA:65,1 +DA:76,1 +DA:77,1 +DA:81,1 +DA:82,1 +DA:86,1 +DA:87,1 +DA:91,1 +DA:92,1 +DA:102,1 +DA:103,1 +DA:107,1 +DA:108,1 +DA:112,1 +DA:113,1 +LF:30 +LH:30 +end_of_record +SF:lib/src/types/camera_description.dart +DA:8,2 +DA:10,2 +DA:13,2 +DA:16,2 +DA:22,0 +DA:39,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:46,0 +DA:48,0 +DA:51,0 +DA:53,0 +LF:13 +LH:4 +end_of_record +SF:lib/src/types/camera_event.dart +DA:17,0 +DA:42,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:53,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:65,2 +DA:69,2 +DA:72,2 +DA:75,2 +LF:17 +LH:4 +end_of_record +SF:lib/src/types/resolution_preset.dart +DA:4,2 +DA:6,2 +DA:9,2 +DA:12,2 +DA:15,2 +DA:18,2 +DA:21,2 +LF:7 +LH:7 +end_of_record diff --git a/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart b/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart new file mode 100644 index 000000000000..09a7ac7089a6 --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart @@ -0,0 +1,6 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'src/platform_interface/camera_platform.dart'; +export 'src/types/types.dart'; 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 new file mode 100644 index 000000000000..6342224bd9ea --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -0,0 +1,12 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// 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'; + +/// An implementation of [CameraPlatform] that uses method channels. +class MethodChannelCamera extends CameraPlatform { + // TODO(mvanbeusekom): Add default method channel implementation of the + // [CameraPlatform] interface. + +} 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 new file mode 100644 index 000000000000..4d7e1b80cc0d --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -0,0 +1,115 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; + +import '../../camera_platform_interface.dart'; + +/// The interface that implementations of camera must implement. +/// +/// Platform implementations should extend this class rather than implement it as `camera` +/// does not consider newly added methods to be breaking changes. Extending this class +/// (using `extends`) ensures that the subclass will get the default implementation, while +/// platform implementations that `implements` this interface will be broken by newly added +/// [CameraPlatform] methods. +abstract class CameraPlatform extends PlatformInterface { + /// Constructs a CameraPlatform. + CameraPlatform() : super(token: _token); + + static final Object _token = Object(); + + static CameraPlatform _instance = MethodChannelCamera(); + + /// The default instance of [CameraPlatform] to use. + /// + /// Defaults to [MethodChannelCamera]. + static CameraPlatform get instance => _instance; + + /// Platform-specific plugins should set this with their own platform-specific + /// class that extends [CameraPlatform] when they register themselves. + static set instance(CameraPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// Completes with a list of available cameras. + Future> availableCameras() { + throw UnimplementedError('availableCameras() is not implemented.'); + } + + /// Initializes the camera on the device and returns its textureId. + Future initializeCamera( + CameraDescription cameraDescription, { + ResolutionPreset resolutionPreset, + bool enableAudio, + }) { + throw UnimplementedError('initializeCamera() is not implemented.'); + } + + /// Returns a Stream of [CameraEvent]s. + Stream cameraEventsFor(int textureId) { + throw UnimplementedError('videoEventsFor() has not been implemented.'); + } + + /// Captures an image and saves it to [path]. + Future takePicture(int textureId, String path) { + throw UnimplementedError('takePicture() is not implemented.'); + } + + /// Prepare the capture session for video recording. + Future prepareForVideoRecording() { + throw UnimplementedError('prepareForVideoRecording() is not implemented.'); + } + + /// Start a video recording and save the file to [path]. + /// + /// A path can for example be obtained using + /// [path_provider](https://pub.dartlang.org/packages/path_provider). + /// + /// The file is written on the flight as the video is being recorded. + /// If a file already exists at the provided path an error will be thrown. + /// The file can be read as soon as [stopVideoRecording] returns. + Future startVideoRecording(int textureId, String path) { + throw UnimplementedError('startVideoRecording() is not implemented.'); + } + + /// Stop the video recording. + Future stopVideoRecording(int textureId) { + throw UnimplementedError('stopVideoRecording() is not implemented.'); + } + + /// Pause video recording. + Future pauseVideoRecording(int textureId) { + throw UnimplementedError('pauseVideoRecording() is not implemented.'); + } + + /// Resume video recording after pausing. + Future resumeVideoRecording(int textureId) { + throw UnimplementedError('resumeVideoRecording() is not implemented.'); + } + + /// Start streaming images from platform camera. + /// + /// When running continuously with [CameraPreview] widget, this function runs + /// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can + /// have significant frame rate drops for [CameraPreview] on lower end + /// devices. + // TODO(bmparr): Add settings for resolution and fps. + Future startImageStream(onLatestImageAvailable onAvailable) { + throw UnimplementedError('startImageStream() is not implemented.'); + } + + /// Stop streaming images from platform camera. + Future stopImageStream() { + throw UnimplementedError('stopImageStream() is not implemented.'); + } + + /// Releases the resources of this camera. + Future dispose(int textureId) { + throw UnimplementedError('dispose() is not implemented.'); + } +} diff --git a/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart b/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart new file mode 100644 index 000000000000..edef7929a761 --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart @@ -0,0 +1,11 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'types.dart'; + +/// Signature for a callback receiving the a camera image. +/// +/// This is used by [CameraPlatform.startImageStream]. +// ignore: inference_failure_on_function_return_type +typedef onLatestImageAvailable = Function(CameraImage image); diff --git a/packages/camera/camera_platform_interface/lib/src/types/camera_description.dart b/packages/camera/camera_platform_interface/lib/src/types/camera_description.dart new file mode 100644 index 000000000000..7a11e921b5f6 --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/camera_description.dart @@ -0,0 +1,55 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +/// The direction the camera is facing. +enum CameraLensDirection { + /// Front facing camera (a user looking at the screen is seen by the camera). + front, + + /// Back facing camera (a user looking at the screen is not seen by the camera). + back, + + /// External camera which may not be mounted to the device. + external, +} + +/// Properties of a camera device. +class CameraDescription { + /// Creates a new camera description with the given properties. + CameraDescription({this.name, this.lensDirection, this.sensorOrientation}); + + /// The name of the camera device. + final String name; + + /// The direction the camera is facing. + final CameraLensDirection lensDirection; + + /// Clockwise angle through which the output image needs to be rotated to be upright on the device screen in its native orientation. + /// + /// **Range of valid values:** + /// 0, 90, 180, 270 + /// + /// On Android, also defines the direction of rolling shutter readout, which + /// is from top to bottom in the sensor's coordinate system. + final int sensorOrientation; + + @override + bool operator ==(Object o) { + return o is CameraDescription && + o.name == name && + o.lensDirection == lensDirection; + } + + @override + int get hashCode { + return hashValues(name, lensDirection); + } + + @override + String toString() { + return '$runtimeType($name, $lensDirection, $sensorOrientation)'; + } +} diff --git a/packages/camera/camera_platform_interface/lib/src/types/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/types/camera_event.dart new file mode 100644 index 000000000000..7b9b47381e28 --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/camera_event.dart @@ -0,0 +1,76 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +import 'package:meta/meta.dart'; + +/// Event emitted from the platform implementation. +class CameraEvent { + /// Creates an instance of [CameraEvent]. + /// + /// The [eventType] argument is required. + /// + /// Depending on the [eventType], the [captureSize], [previewSize] and + /// [errorDescription] arguments can be null. + CameraEvent({ + @required this.eventType, + this.captureSize, + this.previewSize, + this.errorDescription, + }); + + /// The type of the event. + final CameraEventType eventType; + + /// The capture size in pixels. + /// + /// Only used if the [eventType] is [CameraEventType.initialized] + final Size captureSize; + + /// The size of the preview in pixels. + /// + /// Only used if the [eventType] is [CameraEventType.initialized] + final Size previewSize; + + /// Description of the error. + /// + /// Only used if [eventType] is [CameraEventType.error]. + final String errorDescription; + + @override + bool operator ==(Object other) { + return identical(this, other) || + other is CameraEvent && + runtimeType == other.runtimeType && + eventType == other.eventType && + captureSize == other.captureSize && + previewSize == other.previewSize && + errorDescription == other.errorDescription; + } + + @override + int get hashCode => + eventType.hashCode ^ + captureSize.hashCode ^ + previewSize.hashCode ^ + errorDescription.hashCode; +} + +/// Type of the event. +/// +/// Emitted by the platform implementation when the camera is closing or +/// if an error occured. +enum CameraEventType { + /// The camera has been initialized. + + /// The camera is closing. + cameraClosing, + + /// An error occured while accessing the camera. + error, + + /// An unknown event has been received. + unknown, +} diff --git a/packages/camera/camera_platform_interface/lib/src/types/camera_image.dart b/packages/camera/camera_platform_interface/lib/src/types/camera_image.dart new file mode 100644 index 000000000000..5662f130d8a9 --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/camera_image.dart @@ -0,0 +1,148 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; + +/// A single color plane of image data. +/// +/// The number and meaning of the planes in an image are determined by the +/// format of the Image. +class Plane { + Plane._fromPlatformData(Map data) + : bytes = data['bytes'], + bytesPerPixel = data['bytesPerPixel'], + bytesPerRow = data['bytesPerRow'], + height = data['height'], + width = data['width']; + + /// Bytes representing this plane. + final Uint8List bytes; + + /// The distance between adjacent pixel samples on Android, in bytes. + /// + /// Will be `null` on iOS. + final int bytesPerPixel; + + /// The row stride for this color plane, in bytes. + final int bytesPerRow; + + /// Height of the pixel buffer on iOS. + /// + /// Will be `null` on Android + final int height; + + /// Width of the pixel buffer on iOS. + /// + /// Will be `null` on Android. + final int width; +} + +// TODO:(bmparr) Turn [ImageFormatGroup] to a class with int values. +/// Group of image formats that are comparable across Android and iOS platforms. +enum ImageFormatGroup { + /// The image format does not fit into any specific group. + unknown, + + /// Multi-plane YUV 420 format. + /// + /// This format is a generic YCbCr format, capable of describing any 4:2:0 + /// chroma-subsampled planar or semiplanar buffer (but not fully interleaved), + /// with 8 bits per color sample. + /// + /// On Android, this is `android.graphics.ImageFormat.YUV_420_888`. See + /// https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888 + /// + /// On iOS, this is `kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange`. See + /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_420ypcbcr8biplanarvideorange?language=objc + yuv420, + + /// 32-bit BGRA. + /// + /// On iOS, this is `kCVPixelFormatType_32BGRA`. See + /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_32bgra?language=objc + bgra8888, +} + +/// Describes how pixels are represented in an image. +class ImageFormat { + ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw); + + /// Describes the format group the raw image format falls into. + final ImageFormatGroup group; + + /// Raw version of the format from the Android or iOS platform. + /// + /// On Android, this is an `int` from class `android.graphics.ImageFormat`. See + /// https://developer.android.com/reference/android/graphics/ImageFormat + /// + /// On iOS, this is a `FourCharCode` constant from Pixel Format Identifiers. + /// See https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers?language=objc + final dynamic raw; +} + +ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) { + if (defaultTargetPlatform == TargetPlatform.android) { + // android.graphics.ImageFormat.YUV_420_888 + if (rawFormat == 35) { + return ImageFormatGroup.yuv420; + } + } + + if (defaultTargetPlatform == TargetPlatform.iOS) { + switch (rawFormat) { + // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + case 875704438: + return ImageFormatGroup.yuv420; + // kCVPixelFormatType_32BGRA + case 1111970369: + return ImageFormatGroup.bgra8888; + } + } + + return ImageFormatGroup.unknown; +} + +/// A single complete image buffer from the platform camera. +/// +/// This class allows for direct application access to the pixel data of an +/// Image through one or more [Uint8List]. Each buffer is encapsulated in a +/// [Plane] that describes the layout of the pixel data in that plane. The +/// [CameraImage] is not directly usable as a UI resource. +/// +/// Although not all image formats are planar on iOS, we treat 1-dimensional +/// images as single planar images. +class CameraImage { + /// Creates a new [CameraImage] from the data received from the platform. + CameraImage.fromPlatformData(Map data) + : format = ImageFormat._fromPlatformData(data['format']), + height = data['height'], + width = data['width'], + planes = List.unmodifiable(data['planes'] + .map((dynamic planeData) => Plane._fromPlatformData(planeData))); + + /// Format of the image provided. + /// + /// Determines the number of planes needed to represent the image, and + /// the general layout of the pixel data in each [Uint8List]. + final ImageFormat format; + + /// Height of the image in pixels. + /// + /// For formats where some color channels are subsampled, this is the height + /// of the largest-resolution plane. + final int height; + + /// Width of the image in pixels. + /// + /// For formats where some color channels are subsampled, this is the width + /// of the largest-resolution plane. + final int width; + + /// The pixels planes for this image. + /// + /// The number of planes is determined by the format of the image. + final List planes; +} diff --git a/packages/camera/camera_platform_interface/lib/src/types/resolution_preset.dart b/packages/camera/camera_platform_interface/lib/src/types/resolution_preset.dart new file mode 100644 index 000000000000..2ffb718efa17 --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/resolution_preset.dart @@ -0,0 +1,22 @@ +/// Affect the quality of video recording and image capture: +/// +/// If a preset is not available on the camera being used a preset of lower quality will be selected automatically. +enum ResolutionPreset { + /// 352x288 on iOS, 240p (320x240) on Android + low, + + /// 480p (640x480 on iOS, 720x480 on Android) + medium, + + /// 720p (1280x720) + high, + + /// 1080p (1920x1080) + veryHigh, + + /// 2160p (3840x2160) + ultraHigh, + + /// The highest resolution available. + max, +} diff --git a/packages/camera/camera_platform_interface/lib/src/types/types.dart b/packages/camera/camera_platform_interface/lib/src/types/types.dart new file mode 100644 index 000000000000..faa0f554526a --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/types.dart @@ -0,0 +1,9 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'callbacks.dart'; +export 'camera_description.dart'; +export 'camera_event.dart'; +export 'camera_image.dart'; +export 'resolution_preset.dart'; diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml new file mode 100644 index 000000000000..308665afdd78 --- /dev/null +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -0,0 +1,22 @@ +name: camera_platform_interface +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: 0.1.0 + +dependencies: + flutter: + sdk: flutter + meta: ^1.0.5 + plugin_platform_interface: ^1.0.1 + +dev_dependencies: + flutter_test: + sdk: flutter + mockito: ^4.1.1 + pedantic: ^1.8.0 + +environment: + sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.22.0 <2.0.0" diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart new file mode 100644 index 000000000000..f8caa41c97a7 --- /dev/null +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -0,0 +1,205 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; +import 'package:mockito/mockito.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$CameraPlatform', () { + test('$MethodChannelCamera is the default instance', () { + expect(CameraPlatform.instance, isA()); + }); + + test('Cannot be implemented with `implements`', () { + expect(() { + CameraPlatform.instance = ImplementsCameraPlatform(); + }, throwsNoSuchMethodError); + }); + + test('Can be extended', () { + CameraPlatform.instance = ExtendsCameraPlatform(); + }); + + test('Can be mocked with `implements`', () { + final mock = MockCameraPlatform(); + CameraPlatform.instance = mock; + }); + + test( + // ignore: lines_longer_than_80_chars + 'Default implementation of availableCameras() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.availableCameras(), + throwsUnimplementedError, + ); + }); + + test( + // ignore: lines_longer_than_80_chars + 'Default implementation of cameraEventsFor() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.cameraEventsFor(1), + throwsUnimplementedError, + ); + }); + + test('Default implementation of dispose() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.dispose(1), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of initialize() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.initializeCamera(null), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of pauseVideoRecording() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.pauseVideoRecording(1), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of prepareForVideoRecording() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.prepareForVideoRecording(), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of resumeVideoRecording() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.resumeVideoRecording(1), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of startImageStream() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.startImageStream(null), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of startVideoRecording() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.startVideoRecording(1, null), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of stopImageStream() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.stopImageStream(), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of stopVideoRecording() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.stopVideoRecording(1), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of takePicture() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.takePicture(1, null), + throwsUnimplementedError, + ); + }); + }); +} + +class ImplementsCameraPlatform implements CameraPlatform { + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} + +class MockCameraPlatform extends Mock + with + // ignore: prefer_mixin + MockPlatformInterfaceMixin + implements + CameraPlatform {} + +class ExtendsCameraPlatform extends CameraPlatform {} diff --git a/packages/camera/camera_platform_interface/test/types/camera_image_test.dart b/packages/camera/camera_platform_interface/test/types/camera_image_test.dart new file mode 100644 index 000000000000..61ed75225cdf --- /dev/null +++ b/packages/camera/camera_platform_interface/test/types/camera_image_test.dart @@ -0,0 +1,113 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:typed_data'; + +import 'package:camera_platform_interface/src/types/camera_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('$CameraImage tests', () { + test('$CameraImage can be created', () { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': 35, + 'height': 1, + 'width': 4, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.height, 1); + expect(cameraImage.width, 4); + expect(cameraImage.format.group, ImageFormatGroup.yuv420); + expect(cameraImage.planes.length, 1); + }); + + test('$CameraImage has ImageFormatGroup.yuv420 for iOS', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': 875704438, + 'height': 1, + 'width': 4, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.format.group, ImageFormatGroup.yuv420); + }); + + test('$CameraImage has ImageFormatGroup.yuv420 for Android', () { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': 35, + 'height': 1, + 'width': 4, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.format.group, ImageFormatGroup.yuv420); + }); + + test('$CameraImage has ImageFormatGroup.bgra8888 for iOS', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': 1111970369, + 'height': 1, + 'width': 4, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.format.group, ImageFormatGroup.bgra8888); + }); + test('$CameraImage has ImageFormatGroup.unknown', () { + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': null, + 'height': 1, + 'width': 4, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.format.group, ImageFormatGroup.unknown); + }); + }); +} From fdac99f181ce80ab2133ea3939b085f15dcababe Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 6 Nov 2020 17:15:10 +0100 Subject: [PATCH 04/89] Remove test coverage folder --- .../coverage/html/amber.png | Bin 141 -> 0 bytes .../coverage/html/emerald.png | Bin 141 -> 0 bytes .../coverage/html/gcov.css | 519 ------------------ .../coverage/html/glass.png | Bin 167 -> 0 bytes .../coverage/html/index-sort-f.html | 103 ---- .../coverage/html/index-sort-l.html | 103 ---- .../coverage/html/index.html | 103 ---- .../camera_platform.dart.func-sort-c.html | 72 --- .../camera_platform.dart.func.html | 72 --- .../camera_platform.dart.gcov.html | 191 ------- .../html/platform_interface/index-sort-f.html | 93 ---- .../html/platform_interface/index-sort-l.html | 93 ---- .../html/platform_interface/index.html | 93 ---- .../coverage/html/ruby.png | Bin 141 -> 0 bytes .../coverage/html/snow.png | Bin 141 -> 0 bytes .../camera_description.dart.func-sort-c.html | 72 --- .../types/camera_description.dart.func.html | 72 --- .../types/camera_description.dart.gcov.html | 131 ----- .../types/camera_event.dart.func-sort-c.html | 72 --- .../html/types/camera_event.dart.func.html | 72 --- .../html/types/camera_event.dart.gcov.html | 152 ----- .../types/camera_image.dart.func-sort-c.html | 72 --- .../html/types/camera_image.dart.func.html | 72 --- .../html/types/camera_image.dart.gcov.html | 224 -------- .../coverage/html/types/index-sort-f.html | 123 ----- .../coverage/html/types/index-sort-l.html | 123 ----- .../coverage/html/types/index.html | 123 ----- .../resolution_preset.dart.func-sort-c.html | 72 --- .../types/resolution_preset.dart.func.html | 72 --- .../types/resolution_preset.dart.gcov.html | 98 ---- .../coverage/html/updown.png | Bin 117 -> 0 bytes .../coverage/lcov.info | 110 ---- 32 files changed, 3102 deletions(-) delete mode 100644 packages/camera/camera_platform_interface/coverage/html/amber.png delete mode 100644 packages/camera/camera_platform_interface/coverage/html/emerald.png delete mode 100644 packages/camera/camera_platform_interface/coverage/html/gcov.css delete mode 100644 packages/camera/camera_platform_interface/coverage/html/glass.png delete mode 100644 packages/camera/camera_platform_interface/coverage/html/index-sort-f.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/index-sort-l.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/index.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/ruby.png delete mode 100644 packages/camera/camera_platform_interface/coverage/html/snow.png delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/index.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html delete mode 100644 packages/camera/camera_platform_interface/coverage/html/updown.png delete mode 100644 packages/camera/camera_platform_interface/coverage/lcov.info diff --git a/packages/camera/camera_platform_interface/coverage/html/amber.png b/packages/camera/camera_platform_interface/coverage/html/amber.png deleted file mode 100644 index 2cab170d8359081983a4e343848dfe06bc490f12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^G2tW}LqE04T&+ z;1OBOz`!j8!i<;h*8KqrvZOouIx;Y9?C1WI$O`1M1^9%x{(levWG?NMQuI!iC1^Jb!lvI6;R0X`wF(yt=9xVZRt1vCRixIA4P dLn>}1Cji+@42)0J?}79&c)I$ztaD0e0sy@GAL0N2 diff --git a/packages/camera/camera_platform_interface/coverage/html/gcov.css b/packages/camera/camera_platform_interface/coverage/html/gcov.css deleted file mode 100644 index bfd0a83e10b5..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/gcov.css +++ /dev/null @@ -1,519 +0,0 @@ -/* All views: initial background and text color */ -body -{ - color: #000000; - background-color: #FFFFFF; -} - -/* All views: standard link format*/ -a:link -{ - color: #284FA8; - text-decoration: underline; -} - -/* All views: standard link - visited format */ -a:visited -{ - color: #00CB40; - text-decoration: underline; -} - -/* All views: standard link - activated format */ -a:active -{ - color: #FF0040; - text-decoration: underline; -} - -/* All views: main title format */ -td.title -{ - text-align: center; - padding-bottom: 10px; - font-family: sans-serif; - font-size: 20pt; - font-style: italic; - font-weight: bold; -} - -/* All views: header item format */ -td.headerItem -{ - text-align: right; - padding-right: 6px; - font-family: sans-serif; - font-weight: bold; - vertical-align: top; - white-space: nowrap; -} - -/* All views: header item value format */ -td.headerValue -{ - text-align: left; - color: #284FA8; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; -} - -/* All views: header item coverage table heading */ -td.headerCovTableHead -{ - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - font-size: 80%; - white-space: nowrap; -} - -/* All views: header item coverage table entry */ -td.headerCovTableEntry -{ - text-align: right; - color: #284FA8; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; - padding-left: 12px; - padding-right: 4px; - background-color: #DAE7FE; -} - -/* All views: header item coverage table entry for high coverage rate */ -td.headerCovTableEntryHi -{ - text-align: right; - color: #000000; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; - padding-left: 12px; - padding-right: 4px; - background-color: #A7FC9D; -} - -/* All views: header item coverage table entry for medium coverage rate */ -td.headerCovTableEntryMed -{ - text-align: right; - color: #000000; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; - padding-left: 12px; - padding-right: 4px; - background-color: #FFEA20; -} - -/* All views: header item coverage table entry for ow coverage rate */ -td.headerCovTableEntryLo -{ - text-align: right; - color: #000000; - font-family: sans-serif; - font-weight: bold; - white-space: nowrap; - padding-left: 12px; - padding-right: 4px; - background-color: #FF0000; -} - -/* All views: header legend value for legend entry */ -td.headerValueLeg -{ - text-align: left; - color: #000000; - font-family: sans-serif; - font-size: 80%; - white-space: nowrap; - padding-top: 4px; -} - -/* All views: color of horizontal ruler */ -td.ruler -{ - background-color: #6688D4; -} - -/* All views: version string format */ -td.versionInfo -{ - text-align: center; - padding-top: 2px; - font-family: sans-serif; - font-style: italic; -} - -/* Directory view/File view (all)/Test case descriptions: - table headline format */ -td.tableHead -{ - text-align: center; - color: #FFFFFF; - background-color: #6688D4; - font-family: sans-serif; - font-size: 120%; - font-weight: bold; - white-space: nowrap; - padding-left: 4px; - padding-right: 4px; -} - -span.tableHeadSort -{ - padding-right: 4px; -} - -/* Directory view/File view (all): filename entry format */ -td.coverFile -{ - text-align: left; - padding-left: 10px; - padding-right: 20px; - color: #284FA8; - background-color: #DAE7FE; - font-family: monospace; -} - -/* Directory view/File view (all): bar-graph entry format*/ -td.coverBar -{ - padding-left: 10px; - padding-right: 10px; - background-color: #DAE7FE; -} - -/* Directory view/File view (all): bar-graph outline color */ -td.coverBarOutline -{ - background-color: #000000; -} - -/* Directory view/File view (all): percentage entry for files with - high coverage rate */ -td.coverPerHi -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #A7FC9D; - font-weight: bold; - font-family: sans-serif; -} - -/* Directory view/File view (all): line count entry for files with - high coverage rate */ -td.coverNumHi -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #A7FC9D; - white-space: nowrap; - font-family: sans-serif; -} - -/* Directory view/File view (all): percentage entry for files with - medium coverage rate */ -td.coverPerMed -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #FFEA20; - font-weight: bold; - font-family: sans-serif; -} - -/* Directory view/File view (all): line count entry for files with - medium coverage rate */ -td.coverNumMed -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #FFEA20; - white-space: nowrap; - font-family: sans-serif; -} - -/* Directory view/File view (all): percentage entry for files with - low coverage rate */ -td.coverPerLo -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #FF0000; - font-weight: bold; - font-family: sans-serif; -} - -/* Directory view/File view (all): line count entry for files with - low coverage rate */ -td.coverNumLo -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #FF0000; - white-space: nowrap; - font-family: sans-serif; -} - -/* File view (all): "show/hide details" link format */ -a.detail:link -{ - color: #B8D0FF; - font-size:80%; -} - -/* File view (all): "show/hide details" link - visited format */ -a.detail:visited -{ - color: #B8D0FF; - font-size:80%; -} - -/* File view (all): "show/hide details" link - activated format */ -a.detail:active -{ - color: #FFFFFF; - font-size:80%; -} - -/* File view (detail): test name entry */ -td.testName -{ - text-align: right; - padding-right: 10px; - background-color: #DAE7FE; - font-family: sans-serif; -} - -/* File view (detail): test percentage entry */ -td.testPer -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #DAE7FE; - font-family: sans-serif; -} - -/* File view (detail): test lines count entry */ -td.testNum -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #DAE7FE; - font-family: sans-serif; -} - -/* Test case descriptions: test name format*/ -dt -{ - font-family: sans-serif; - font-weight: bold; -} - -/* Test case descriptions: description table body */ -td.testDescription -{ - padding-top: 10px; - padding-left: 30px; - padding-bottom: 10px; - padding-right: 30px; - background-color: #DAE7FE; -} - -/* Source code view: function entry */ -td.coverFn -{ - text-align: left; - padding-left: 10px; - padding-right: 20px; - color: #284FA8; - background-color: #DAE7FE; - font-family: monospace; -} - -/* Source code view: function entry zero count*/ -td.coverFnLo -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #FF0000; - font-weight: bold; - font-family: sans-serif; -} - -/* Source code view: function entry nonzero count*/ -td.coverFnHi -{ - text-align: right; - padding-left: 10px; - padding-right: 10px; - background-color: #DAE7FE; - font-weight: bold; - font-family: sans-serif; -} - -/* Source code view: source code format */ -pre.source -{ - font-family: monospace; - white-space: pre; - margin-top: 2px; -} - -/* Source code view: line number format */ -span.lineNum -{ - background-color: #EFE383; -} - -/* Source code view: format for lines which were executed */ -td.lineCov, -span.lineCov -{ - background-color: #CAD7FE; -} - -/* Source code view: format for Cov legend */ -span.coverLegendCov -{ - padding-left: 10px; - padding-right: 10px; - padding-bottom: 2px; - background-color: #CAD7FE; -} - -/* Source code view: format for lines which were not executed */ -td.lineNoCov, -span.lineNoCov -{ - background-color: #FF6230; -} - -/* Source code view: format for NoCov legend */ -span.coverLegendNoCov -{ - padding-left: 10px; - padding-right: 10px; - padding-bottom: 2px; - background-color: #FF6230; -} - -/* Source code view (function table): standard link - visited format */ -td.lineNoCov > a:visited, -td.lineCov > a:visited -{ - color: black; - text-decoration: underline; -} - -/* Source code view: format for lines which were executed only in a - previous version */ -span.lineDiffCov -{ - background-color: #B5F7AF; -} - -/* Source code view: format for branches which were executed - * and taken */ -span.branchCov -{ - background-color: #CAD7FE; -} - -/* Source code view: format for branches which were executed - * but not taken */ -span.branchNoCov -{ - background-color: #FF6230; -} - -/* Source code view: format for branches which were not executed */ -span.branchNoExec -{ - background-color: #FF6230; -} - -/* Source code view: format for the source code heading line */ -pre.sourceHeading -{ - white-space: pre; - font-family: monospace; - font-weight: bold; - margin: 0px; -} - -/* All views: header legend value for low rate */ -td.headerValueLegL -{ - font-family: sans-serif; - text-align: center; - white-space: nowrap; - padding-left: 4px; - padding-right: 2px; - background-color: #FF0000; - font-size: 80%; -} - -/* All views: header legend value for med rate */ -td.headerValueLegM -{ - font-family: sans-serif; - text-align: center; - white-space: nowrap; - padding-left: 2px; - padding-right: 2px; - background-color: #FFEA20; - font-size: 80%; -} - -/* All views: header legend value for hi rate */ -td.headerValueLegH -{ - font-family: sans-serif; - text-align: center; - white-space: nowrap; - padding-left: 2px; - padding-right: 4px; - background-color: #A7FC9D; - font-size: 80%; -} - -/* All views except source code view: legend format for low coverage */ -span.coverLegendCovLo -{ - padding-left: 10px; - padding-right: 10px; - padding-top: 2px; - background-color: #FF0000; -} - -/* All views except source code view: legend format for med coverage */ -span.coverLegendCovMed -{ - padding-left: 10px; - padding-right: 10px; - padding-top: 2px; - background-color: #FFEA20; -} - -/* All views except source code view: legend format for hi coverage */ -span.coverLegendCovHi -{ - padding-left: 10px; - padding-right: 10px; - padding-top: 2px; - background-color: #A7FC9D; -} diff --git a/packages/camera/camera_platform_interface/coverage/html/glass.png b/packages/camera/camera_platform_interface/coverage/html/glass.png deleted file mode 100644 index e1abc00680a3093c49fdb775ae6bdb6764c95af2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)gaEa{HEjtmSN`?>!lvI6;R0X`wF z|Ns97GD8ntt^-nxB|(0{3=Yq3q=7g|-tI089jvk*Kn`btM`SSr1Gf+eGhVt|_XjA* zUgGKN%6^Gmn4d%Ph(nkFP>9RZ#WAE}PI3Z}&BVayv3^M*kj3EX>gTe~DWM4f=_Dpv diff --git a/packages/camera/camera_platform_interface/coverage/html/index-sort-f.html b/packages/camera/camera_platform_interface/coverage/html/index-sort-f.html deleted file mode 100644 index 6615ccc56576..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/index-sort-f.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top levelHitTotalCoverage
Test:lcov.infoLines:689075.6 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Directory Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
platform_interface -
100.0%
-
100.0 %30 / 30-0 / 0
types -
63.3%63.3%
-
63.3 %38 / 60-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/index-sort-l.html b/packages/camera/camera_platform_interface/coverage/html/index-sort-l.html deleted file mode 100644 index 738b57014f22..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/index-sort-l.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top levelHitTotalCoverage
Test:lcov.infoLines:689075.6 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Directory Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
types -
63.3%63.3%
-
63.3 %38 / 60-0 / 0
platform_interface -
100.0%
-
100.0 %30 / 30-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/index.html b/packages/camera/camera_platform_interface/coverage/html/index.html deleted file mode 100644 index 4a459d170aa7..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/index.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top levelHitTotalCoverage
Test:lcov.infoLines:689075.6 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Directory Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
platform_interface -
100.0%
-
100.0 %30 / 30-0 / 0
types -
63.3%63.3%
-
63.3 %38 / 60-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html deleted file mode 100644 index 1ed65bcc45af..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func-sort-c.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - platform_interface - camera_platform.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html deleted file mode 100644 index eadf62bde488..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.func.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - platform_interface - camera_platform.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html deleted file mode 100644 index b667827592e8..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/platform_interface/camera_platform.dart.gcov.html +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - platform_interface - camera_platform.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- - - - - - - - -

-
          Line data    Source code
-
-       1             : // Copyright 2018 The Chromium Authors. All rights reserved.
-       2             : // Use of this source code is governed by a BSD-style license that can be
-       3             : // found in the LICENSE file.
-       4             : 
-       5             : import 'dart:async';
-       6             : 
-       7             : import 'package:plugin_platform_interface/plugin_platform_interface.dart';
-       8             : import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart';
-       9             : 
-      10             : import '../../camera_platform_interface.dart';
-      11             : 
-      12             : /// The interface that implementations of camera must implement.
-      13             : ///
-      14             : /// Platform implementations should extend this class rather than implement it as `camera`
-      15             : /// does not consider newly added methods to be breaking changes. Extending this class
-      16             : /// (using `extends`) ensures that the subclass will get the default implementation, while
-      17             : /// platform implementations that `implements` this interface will be broken by newly added
-      18             : /// [CameraPlatform] methods.
-      19             : abstract class CameraPlatform extends PlatformInterface {
-      20             :   /// Constructs a CameraPlatform.
-      21           3 :   CameraPlatform() : super(token: _token);
-      22             : 
-      23           3 :   static final Object _token = Object();
-      24             : 
-      25           3 :   static CameraPlatform _instance = MethodChannelCamera();
-      26             : 
-      27             :   /// The default instance of [CameraPlatform] to use.
-      28             :   ///
-      29             :   /// Defaults to [MethodChannelCamera].
-      30           2 :   static CameraPlatform get instance => _instance;
-      31             : 
-      32             :   /// Platform-specific plugins should set this with their own platform-specific
-      33             :   /// class that extends [CameraPlatform] when they register themselves.
-      34           1 :   static set instance(CameraPlatform instance) {
-      35           2 :     PlatformInterface.verifyToken(instance, _token);
-      36             :     _instance = instance;
-      37             :   }
-      38             : 
-      39             :   /// Completes with a list of available cameras.
-      40           1 :   Future<List<CameraDescription>> availableCameras() {
-      41           1 :     throw UnimplementedError('availableCameras() is not implemented.');
-      42             :   }
-      43             : 
-      44             :   /// Initializes the camera on the device and returns its textureId.
-      45           1 :   Future<int> initializeCamera(
-      46             :     CameraDescription cameraDescription, {
-      47             :     ResolutionPreset resolutionPreset,
-      48             :     bool enableAudio,
-      49             :   }) {
-      50           1 :     throw UnimplementedError('initializeCamera() is not implemented.');
-      51             :   }
-      52             : 
-      53             :   /// Returns a Stream of [CameraEvent]s.
-      54           1 :   Stream<CameraEvent> cameraEventsFor(int textureId) {
-      55           1 :     throw UnimplementedError('videoEventsFor() has not been implemented.');
-      56             :   }
-      57             : 
-      58             :   /// Captures an image and saves it to [path].
-      59           1 :   Future<void> takePicture(int textureId, String path) {
-      60           1 :     throw UnimplementedError('takePicture() is not implemented.');
-      61             :   }
-      62             : 
-      63             :   /// Prepare the capture session for video recording.
-      64           1 :   Future<void> prepareForVideoRecording() {
-      65           1 :     throw UnimplementedError('prepareForVideoRecording() is not implemented.');
-      66             :   }
-      67             : 
-      68             :   /// Start a video recording and save the file to [path].
-      69             :   ///
-      70             :   /// A path can for example be obtained using
-      71             :   /// [path_provider](https://pub.dartlang.org/packages/path_provider).
-      72             :   ///
-      73             :   /// The file is written on the flight as the video is being recorded.
-      74             :   /// If a file already exists at the provided path an error will be thrown.
-      75             :   /// The file can be read as soon as [stopVideoRecording] returns.
-      76           1 :   Future<void> startVideoRecording(int textureId, String path) {
-      77           1 :     throw UnimplementedError('startVideoRecording() is not implemented.');
-      78             :   }
-      79             : 
-      80             :   /// Stop the video recording.
-      81           1 :   Future<void> stopVideoRecording(int textureId) {
-      82           1 :     throw UnimplementedError('stopVideoRecording() is not implemented.');
-      83             :   }
-      84             : 
-      85             :   /// Pause video recording.
-      86           1 :   Future<void> pauseVideoRecording(int textureId) {
-      87           1 :     throw UnimplementedError('pauseVideoRecording() is not implemented.');
-      88             :   }
-      89             : 
-      90             :   /// Resume video recording after pausing.
-      91           1 :   Future<void> resumeVideoRecording(int textureId) {
-      92           1 :     throw UnimplementedError('resumeVideoRecording() is not implemented.');
-      93             :   }
-      94             : 
-      95             :   /// Start streaming images from platform camera.
-      96             :   ///
-      97             :   /// When running continuously with [CameraPreview] widget, this function runs
-      98             :   /// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can
-      99             :   /// have significant frame rate drops for [CameraPreview] on lower end
-     100             :   /// devices.
-     101             :   // TODO(bmparr): Add settings for resolution and fps.
-     102           1 :   Future<void> startImageStream(onLatestImageAvailable onAvailable) {
-     103           1 :     throw UnimplementedError('startImageStream() is not implemented.');
-     104             :   }
-     105             : 
-     106             :   /// Stop streaming images from platform camera.
-     107           1 :   Future<void> stopImageStream() {
-     108           1 :     throw UnimplementedError('stopImageStream() is not implemented.');
-     109             :   }
-     110             : 
-     111             :   /// Releases the resources of this camera.
-     112           1 :   Future<void> dispose(int textureId) {
-     113           1 :     throw UnimplementedError('dispose() is not implemented.');
-     114             :   }
-     115             : }
-
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html deleted file mode 100644 index 58bbd9987ed2..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-f.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - platform_interfaceHitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - -

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_platform.dart -
100.0%
-
100.0 %30 / 30-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html deleted file mode 100644 index d4f9ff9f5ca9..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index-sort-l.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - platform_interfaceHitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - -

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_platform.dart -
100.0%
-
100.0 %30 / 30-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html b/packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html deleted file mode 100644 index 413082c13942..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/platform_interface/index.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - platform_interfaceHitTotalCoverage
Test:lcov.infoLines:3030100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - -

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_platform.dart -
100.0%
-
100.0 %30 / 30-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/ruby.png b/packages/camera/camera_platform_interface/coverage/html/ruby.png deleted file mode 100644 index 991b6d4ec9e78be165e3ef757eed1aada287364d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^FceV#7`HfI^%F z9+AZi4BSE>%y{W;-5;PJOS+@4BLl<6e(pbstUx|nfKQ0)e^Y%R^MdiLxj>4`)5S5Q b;#P73kj=!v_*DHKNFRfztDnm{r-UW|iOwIS diff --git a/packages/camera/camera_platform_interface/coverage/html/snow.png b/packages/camera/camera_platform_interface/coverage/html/snow.png deleted file mode 100644 index 2cdae107fceec6e7f02ac7acb4a34a82a540caa5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)ga>?NMQuI!iC1^MM!lvI6;R0X`wF|Ns97GD8ntt^-nBo-U3d c6}OTTfNUlP#;5A{K>8RwUHx3vIVCg!071?oo&W#< diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html deleted file mode 100644 index f9ec1768180f..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func-sort-c.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_description.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41330.8 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html deleted file mode 100644 index 4031fb3e2412..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.func.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_description.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41330.8 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html deleted file mode 100644 index 10699d953f8e..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_description.dart.gcov.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_description.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41330.8 %
Date:2020-11-06 16:26:41Functions:00-
-
- - - - - - - - -

-
          Line data    Source code
-
-       1             : // Copyright 2018 The Chromium Authors. All rights reserved.
-       2             : // Use of this source code is governed by a BSD-style license that can be
-       3             : // found in the LICENSE file.
-       4             : 
-       5             : import 'dart:ui';
-       6             : 
-       7             : /// The direction the camera is facing.
-       8           2 : enum CameraLensDirection {
-       9             :   /// Front facing camera (a user looking at the screen is seen by the camera).
-      10           2 :   front,
-      11             : 
-      12             :   /// Back facing camera (a user looking at the screen is not seen by the camera).
-      13           2 :   back,
-      14             : 
-      15             :   /// External camera which may not be mounted to the device.
-      16           2 :   external,
-      17             : }
-      18             : 
-      19             : /// Properties of a camera device.
-      20             : class CameraDescription {
-      21             :   /// Creates a new camera description with the given properties.
-      22           0 :   CameraDescription({this.name, this.lensDirection, this.sensorOrientation});
-      23             : 
-      24             :   /// The name of the camera device.
-      25             :   final String name;
-      26             : 
-      27             :   /// The direction the camera is facing.
-      28             :   final CameraLensDirection lensDirection;
-      29             : 
-      30             :   /// Clockwise angle through which the output image needs to be rotated to be upright on the device screen in its native orientation.
-      31             :   ///
-      32             :   /// **Range of valid values:**
-      33             :   /// 0, 90, 180, 270
-      34             :   ///
-      35             :   /// On Android, also defines the direction of rolling shutter readout, which
-      36             :   /// is from top to bottom in the sensor's coordinate system.
-      37             :   final int sensorOrientation;
-      38             : 
-      39           0 :   @override
-      40             :   bool operator ==(Object o) {
-      41           0 :     return o is CameraDescription &&
-      42           0 :         o.name == name &&
-      43           0 :         o.lensDirection == lensDirection;
-      44             :   }
-      45             : 
-      46           0 :   @override
-      47             :   int get hashCode {
-      48           0 :     return hashValues(name, lensDirection);
-      49             :   }
-      50             : 
-      51           0 :   @override
-      52             :   String toString() {
-      53           0 :     return '$runtimeType($name, $lensDirection, $sensorOrientation)';
-      54             :   }
-      55             : }
-
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html deleted file mode 100644 index f3a43cd1a63b..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func-sort-c.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_event.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41723.5 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html deleted file mode 100644 index f364a44cca63..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.func.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_event.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41723.5 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html deleted file mode 100644 index 6b9684af05ce..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_event.dart.gcov.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_event.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:41723.5 %
Date:2020-11-06 16:26:41Functions:00-
-
- - - - - - - - -

-
          Line data    Source code
-
-       1             : // Copyright 2017 The Chromium Authors. All rights reserved.
-       2             : // Use of this source code is governed by a BSD-style license that can be
-       3             : // found in the LICENSE file.
-       4             : 
-       5             : import 'dart:ui';
-       6             : 
-       7             : import 'package:meta/meta.dart';
-       8             : 
-       9             : /// Event emitted from the platform implementation.
-      10             : class CameraEvent {
-      11             :   /// Creates an instance of [CameraEvent].
-      12             :   ///
-      13             :   /// The [eventType] argument is required.
-      14             :   ///
-      15             :   /// Depending on the [eventType], the [captureSize], [previewSize] and 
-      16             :   /// [errorDescription] arguments can be null.
-      17           0 :   CameraEvent({
-      18             :     @required this.eventType,
-      19             :     this.captureSize,
-      20             :     this.previewSize,
-      21             :     this.errorDescription,
-      22             :   });
-      23             : 
-      24             :   /// The type of the event.
-      25             :   final CameraEventType eventType;
-      26             : 
-      27             :   /// The capture size in pixels.
-      28             :   /// 
-      29             :   /// Only used if the [eventType] is [CameraEventType.initialized]
-      30             :   final Size captureSize;
-      31             : 
-      32             :   /// The size of the preview in pixels.
-      33             :   /// 
-      34             :   /// Only used if the [eventType] is [CameraEventType.initialized]
-      35             :   final Size previewSize;
-      36             : 
-      37             :   /// Description of the error.
-      38             :   ///
-      39             :   /// Only used if [eventType] is [CameraEventType.error].
-      40             :   final String errorDescription;
-      41             : 
-      42           0 :   @override
-      43             :   bool operator ==(Object other) {
-      44             :     return identical(this, other) ||
-      45           0 :         other is CameraEvent &&
-      46           0 :             runtimeType == other.runtimeType &&
-      47           0 :             eventType == other.eventType &&
-      48           0 :             captureSize == other.captureSize &&
-      49           0 :             previewSize == other.previewSize &&
-      50           0 :             errorDescription == other.errorDescription;
-      51             :   }
-      52             : 
-      53           0 :   @override
-      54             :   int get hashCode =>
-      55           0 :       eventType.hashCode ^
-      56           0 :       captureSize.hashCode ^
-      57           0 :       previewSize.hashCode ^
-      58           0 :       errorDescription.hashCode;
-      59             : }
-      60             : 
-      61             : /// Type of the event.
-      62             : ///
-      63             : /// Emitted by the platform implementation when the camera is closing or
-      64             : /// if an error occured.
-      65           2 : enum CameraEventType {
-      66             :   /// The camera has been initialized.
-      67             : 
-      68             :   /// The camera is closing.
-      69           2 :   cameraClosing,
-      70             : 
-      71             :   /// An error occured while accessing the camera.
-      72           2 :   error,
-      73             : 
-      74             :   /// An unknown event has been received.
-      75           2 :   unknown,
-      76             : }
-
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html deleted file mode 100644 index 5cf21835a9fc..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func-sort-c.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_image.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:2323100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html deleted file mode 100644 index 945e78a95d84..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.func.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_image.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:2323100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html deleted file mode 100644 index 96ab1f424ec4..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/camera_image.dart.gcov.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - camera_image.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:2323100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- - - - - - - - -

-
          Line data    Source code
-
-       1             : // Copyright 2018 The Chromium Authors. All rights reserved.
-       2             : // Use of this source code is governed by a BSD-style license that can be
-       3             : // found in the LICENSE file.
-       4             : 
-       5             : import 'dart:typed_data';
-       6             : 
-       7             : import 'package:flutter/foundation.dart';
-       8             : 
-       9             : /// A single color plane of image data.
-      10             : ///
-      11             : /// The number and meaning of the planes in an image are determined by the
-      12             : /// format of the Image.
-      13             : class Plane {
-      14           1 :   Plane._fromPlatformData(Map<dynamic, dynamic> data)
-      15           1 :       : bytes = data['bytes'],
-      16           1 :         bytesPerPixel = data['bytesPerPixel'],
-      17           1 :         bytesPerRow = data['bytesPerRow'],
-      18           1 :         height = data['height'],
-      19           1 :         width = data['width'];
-      20             : 
-      21             :   /// Bytes representing this plane.
-      22             :   final Uint8List bytes;
-      23             : 
-      24             :   /// The distance between adjacent pixel samples on Android, in bytes.
-      25             :   ///
-      26             :   /// Will be `null` on iOS.
-      27             :   final int bytesPerPixel;
-      28             : 
-      29             :   /// The row stride for this color plane, in bytes.
-      30             :   final int bytesPerRow;
-      31             : 
-      32             :   /// Height of the pixel buffer on iOS.
-      33             :   ///
-      34             :   /// Will be `null` on Android
-      35             :   final int height;
-      36             : 
-      37             :   /// Width of the pixel buffer on iOS.
-      38             :   ///
-      39             :   /// Will be `null` on Android.
-      40             :   final int width;
-      41             : }
-      42             : 
-      43             : // TODO:(bmparr) Turn [ImageFormatGroup] to a class with int values.
-      44             : /// Group of image formats that are comparable across Android and iOS platforms.
-      45           2 : enum ImageFormatGroup {
-      46             :   /// The image format does not fit into any specific group.
-      47           2 :   unknown,
-      48             : 
-      49             :   /// Multi-plane YUV 420 format.
-      50             :   ///
-      51             :   /// This format is a generic YCbCr format, capable of describing any 4:2:0
-      52             :   /// chroma-subsampled planar or semiplanar buffer (but not fully interleaved),
-      53             :   /// with 8 bits per color sample.
-      54             :   ///
-      55             :   /// On Android, this is `android.graphics.ImageFormat.YUV_420_888`. See
-      56             :   /// https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888
-      57             :   ///
-      58             :   /// On iOS, this is `kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange`. See
-      59             :   /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_420ypcbcr8biplanarvideorange?language=objc
-      60           2 :   yuv420,
-      61             : 
-      62             :   /// 32-bit BGRA.
-      63             :   ///
-      64             :   /// On iOS, this is `kCVPixelFormatType_32BGRA`. See
-      65             :   /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_32bgra?language=objc
-      66           2 :   bgra8888,
-      67             : }
-      68             : 
-      69             : /// Describes how pixels are represented in an image.
-      70             : class ImageFormat {
-      71           2 :   ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw);
-      72             : 
-      73             :   /// Describes the format group the raw image format falls into.
-      74             :   final ImageFormatGroup group;
-      75             : 
-      76             :   /// Raw version of the format from the Android or iOS platform.
-      77             :   ///
-      78             :   /// On Android, this is an `int` from class `android.graphics.ImageFormat`. See
-      79             :   /// https://developer.android.com/reference/android/graphics/ImageFormat
-      80             :   ///
-      81             :   /// On iOS, this is a `FourCharCode` constant from Pixel Format Identifiers.
-      82             :   /// See https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers?language=objc
-      83             :   final dynamic raw;
-      84             : }
-      85             : 
-      86           1 : ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) {
-      87           2 :   if (defaultTargetPlatform == TargetPlatform.android) {
-      88             :     // android.graphics.ImageFormat.YUV_420_888
-      89           1 :     if (rawFormat == 35) {
-      90             :       return ImageFormatGroup.yuv420;
-      91             :     }
-      92             :   }
-      93             : 
-      94           2 :   if (defaultTargetPlatform == TargetPlatform.iOS) {
-      95             :     switch (rawFormat) {
-      96             :       // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
-      97           1 :       case 875704438:
-      98             :         return ImageFormatGroup.yuv420;
-      99             :       // kCVPixelFormatType_32BGRA
-     100           1 :       case 1111970369:
-     101             :         return ImageFormatGroup.bgra8888;
-     102             :     }
-     103             :   }
-     104             : 
-     105             :   return ImageFormatGroup.unknown;
-     106             : }
-     107             : 
-     108             : /// A single complete image buffer from the platform camera.
-     109             : ///
-     110             : /// This class allows for direct application access to the pixel data of an
-     111             : /// Image through one or more [Uint8List]. Each buffer is encapsulated in a
-     112             : /// [Plane] that describes the layout of the pixel data in that plane. The
-     113             : /// [CameraImage] is not directly usable as a UI resource.
-     114             : ///
-     115             : /// Although not all image formats are planar on iOS, we treat 1-dimensional
-     116             : /// images as single planar images.
-     117             : class CameraImage {
-     118             :   /// Creates a new [CameraImage] from the data received from the platform.
-     119           1 :   CameraImage.fromPlatformData(Map<dynamic, dynamic> data)
-     120           2 :       : format = ImageFormat._fromPlatformData(data['format']),
-     121           1 :         height = data['height'],
-     122           1 :         width = data['width'],
-     123           2 :         planes = List<Plane>.unmodifiable(data['planes']
-     124           3 :             .map((dynamic planeData) => Plane._fromPlatformData(planeData)));
-     125             : 
-     126             :   /// Format of the image provided.
-     127             :   ///
-     128             :   /// Determines the number of planes needed to represent the image, and
-     129             :   /// the general layout of the pixel data in each [Uint8List].
-     130             :   final ImageFormat format;
-     131             : 
-     132             :   /// Height of the image in pixels.
-     133             :   ///
-     134             :   /// For formats where some color channels are subsampled, this is the height
-     135             :   /// of the largest-resolution plane.
-     136             :   final int height;
-     137             : 
-     138             :   /// Width of the image in pixels.
-     139             :   ///
-     140             :   /// For formats where some color channels are subsampled, this is the width
-     141             :   /// of the largest-resolution plane.
-     142             :   final int width;
-     143             : 
-     144             :   /// The pixels planes for this image.
-     145             :   ///
-     146             :   /// The number of planes is determined by the format of the image.
-     147             :   final List<Plane> planes;
-     148             : }
-
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html b/packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html deleted file mode 100644 index 5cf500911692..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/index-sort-f.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - typesHitTotalCoverage
Test:lcov.infoLines:386063.3 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
resolution_preset.dart -
100.0%
-
100.0 %7 / 7-0 / 0
camera_description.dart -
30.8%30.8%
-
30.8 %4 / 13-0 / 0
camera_event.dart -
23.5%23.5%
-
23.5 %4 / 17-0 / 0
camera_image.dart -
100.0%
-
100.0 %23 / 23-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html b/packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html deleted file mode 100644 index 913571e880cb..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/index-sort-l.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - typesHitTotalCoverage
Test:lcov.infoLines:386063.3 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_event.dart -
23.5%23.5%
-
23.5 %4 / 17-0 / 0
camera_description.dart -
30.8%30.8%
-
30.8 %4 / 13-0 / 0
resolution_preset.dart -
100.0%
-
100.0 %7 / 7-0 / 0
camera_image.dart -
100.0%
-
100.0 %23 / 23-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/index.html b/packages/camera/camera_platform_interface/coverage/html/types/index.html deleted file mode 100644 index 479a3edaa376..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/index.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - typesHitTotalCoverage
Test:lcov.infoLines:386063.3 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
camera_description.dart -
30.8%30.8%
-
30.8 %4 / 13-0 / 0
camera_event.dart -
23.5%23.5%
-
23.5 %4 / 17-0 / 0
camera_image.dart -
100.0%
-
100.0 %23 / 23-0 / 0
resolution_preset.dart -
100.0%
-
100.0 %7 / 7-0 / 0
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html deleted file mode 100644 index fdf79712000a..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func-sort-c.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - resolution_preset.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:77100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html deleted file mode 100644 index 40ed9b1de628..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.func.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - resolution_preset.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:77100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- -
- - - - - - -

Function Name Sort by function nameHit count Sort by hit count
-
-
- - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html b/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html deleted file mode 100644 index 05c40ae95295..000000000000 --- a/packages/camera/camera_platform_interface/coverage/html/types/resolution_preset.dart.gcov.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - Codestin Search App - - - - - - - - - - - - - - -
LCOV - code coverage report
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current view:top level - types - resolution_preset.dart (source / functions)HitTotalCoverage
Test:lcov.infoLines:77100.0 %
Date:2020-11-06 16:26:41Functions:00-
-
- - - - - - - - -

-
          Line data    Source code
-
-       1             : /// Affect the quality of video recording and image capture:
-       2             : ///
-       3             : /// If a preset is not available on the camera being used a preset of lower quality will be selected automatically.
-       4           2 : enum ResolutionPreset {
-       5             :   /// 352x288 on iOS, 240p (320x240) on Android
-       6           2 :   low,
-       7             : 
-       8             :   /// 480p (640x480 on iOS, 720x480 on Android)
-       9           2 :   medium,
-      10             : 
-      11             :   /// 720p (1280x720)
-      12           2 :   high,
-      13             : 
-      14             :   /// 1080p (1920x1080)
-      15           2 :   veryHigh,
-      16             : 
-      17             :   /// 2160p (3840x2160)
-      18           2 :   ultraHigh,
-      19             : 
-      20             :   /// The highest resolution available.
-      21           2 :   max,
-      22             : }
-
-
-
- - - - -
Generated by: LCOV version 1.15
-
- - - diff --git a/packages/camera/camera_platform_interface/coverage/html/updown.png b/packages/camera/camera_platform_interface/coverage/html/updown.png deleted file mode 100644 index aa56a238b3e6c435265250f9266cd1b8caba0f20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^AT}Qd8;}%R+`Ae`*?77*hG?8mPH5^{)z4*}Q$iB}huR`+ diff --git a/packages/camera/camera_platform_interface/coverage/lcov.info b/packages/camera/camera_platform_interface/coverage/lcov.info deleted file mode 100644 index f8be39954cf2..000000000000 --- a/packages/camera/camera_platform_interface/coverage/lcov.info +++ /dev/null @@ -1,110 +0,0 @@ -SF:lib/src/types/camera_image.dart -DA:14,1 -DA:15,1 -DA:16,1 -DA:17,1 -DA:18,1 -DA:19,1 -DA:45,2 -DA:47,2 -DA:60,2 -DA:66,2 -DA:71,2 -DA:86,1 -DA:87,2 -DA:89,1 -DA:94,2 -DA:97,1 -DA:100,1 -DA:119,1 -DA:120,2 -DA:121,1 -DA:122,1 -DA:123,2 -DA:124,3 -LF:23 -LH:23 -end_of_record -SF:lib/src/platform_interface/camera_platform.dart -DA:21,3 -DA:23,3 -DA:25,3 -DA:30,2 -DA:34,1 -DA:35,2 -DA:40,1 -DA:41,1 -DA:45,1 -DA:50,1 -DA:54,1 -DA:55,1 -DA:59,1 -DA:60,1 -DA:64,1 -DA:65,1 -DA:76,1 -DA:77,1 -DA:81,1 -DA:82,1 -DA:86,1 -DA:87,1 -DA:91,1 -DA:92,1 -DA:102,1 -DA:103,1 -DA:107,1 -DA:108,1 -DA:112,1 -DA:113,1 -LF:30 -LH:30 -end_of_record -SF:lib/src/types/camera_description.dart -DA:8,2 -DA:10,2 -DA:13,2 -DA:16,2 -DA:22,0 -DA:39,0 -DA:41,0 -DA:42,0 -DA:43,0 -DA:46,0 -DA:48,0 -DA:51,0 -DA:53,0 -LF:13 -LH:4 -end_of_record -SF:lib/src/types/camera_event.dart -DA:17,0 -DA:42,0 -DA:45,0 -DA:46,0 -DA:47,0 -DA:48,0 -DA:49,0 -DA:50,0 -DA:53,0 -DA:55,0 -DA:56,0 -DA:57,0 -DA:58,0 -DA:65,2 -DA:69,2 -DA:72,2 -DA:75,2 -LF:17 -LH:4 -end_of_record -SF:lib/src/types/resolution_preset.dart -DA:4,2 -DA:6,2 -DA:9,2 -DA:12,2 -DA:15,2 -DA:18,2 -DA:21,2 -LF:7 -LH:7 -end_of_record From c92210fafce934e6efec2cd39f7705ccc8e6c3fc Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 9 Nov 2020 09:08:58 +0100 Subject: [PATCH 05/89] Update the version to 1.0.0 --- packages/camera/camera_platform_interface/CHANGELOG.md | 2 +- packages/camera/camera_platform_interface/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index 6cf2f5bd17dd..75d169ea1d6a 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,3 +1,3 @@ -## 0.1.0 +## 1.0.0 - Initial open-source release diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 308665afdd78..4d6f4733927c 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: 0.1.0 +version: 1.0.0 dependencies: flutter: From 4d78819c6d6f68d871a9ff251afd98df6b0d92ba Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 9 Nov 2020 09:17:29 +0100 Subject: [PATCH 06/89] Remove redundant analysis overrides --- .../test/camera_platform_interface_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index f8caa41c97a7..f3cd2c9f03c2 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -32,7 +32,6 @@ void main() { }); test( - // ignore: lines_longer_than_80_chars 'Default implementation of availableCameras() should throw unimplemented error', () { // Arrange @@ -46,7 +45,6 @@ void main() { }); test( - // ignore: lines_longer_than_80_chars 'Default implementation of cameraEventsFor() should throw unimplemented error', () { // Arrange From d98a51ee8f8c913ee6b23de22d12cfcfed16398e Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 9 Nov 2020 17:03:55 +0100 Subject: [PATCH 07/89] Renamed onLatestImageAvailableHandler definition --- .../lib/src/platform_interface/camera_platform.dart | 2 +- .../camera_platform_interface/lib/src/types/callbacks.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 4d7e1b80cc0d..4e3a6d8cb3f9 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 @@ -99,7 +99,7 @@ abstract class CameraPlatform extends PlatformInterface { /// have significant frame rate drops for [CameraPreview] on lower end /// devices. // TODO(bmparr): Add settings for resolution and fps. - Future startImageStream(onLatestImageAvailable onAvailable) { + Future startImageStream(ImageAvailableHandler onAvailable) { throw UnimplementedError('startImageStream() is not implemented.'); } diff --git a/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart b/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart index edef7929a761..8cf3a00bc245 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart @@ -8,4 +8,4 @@ import 'types.dart'; /// /// This is used by [CameraPlatform.startImageStream]. // ignore: inference_failure_on_function_return_type -typedef onLatestImageAvailable = Function(CameraImage image); +typedef ImageAvailableHandler = Function(CameraImage image); From 69f573b963c53605dcf291a8dd06775766111590 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 12 Nov 2020 11:37:41 +0100 Subject: [PATCH 08/89] Split CameraEvents into separate streams --- .../lib/camera_platform_interface.dart | 1 + .../lib/src/events/camera_event.dart | 62 +++++++++++++++ .../platform_interface/camera_platform.dart | 48 ++++++------ .../lib/src/types/callbacks.dart | 11 --- .../lib/src/types/camera_event.dart | 76 ------------------- .../lib/src/types/types.dart | 2 - .../test/camera_platform_interface_test.dart | 39 +++++----- 7 files changed, 107 insertions(+), 132 deletions(-) create mode 100644 packages/camera/camera_platform_interface/lib/src/events/camera_event.dart delete mode 100644 packages/camera/camera_platform_interface/lib/src/types/callbacks.dart delete mode 100644 packages/camera/camera_platform_interface/lib/src/types/camera_event.dart diff --git a/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart b/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart index 09a7ac7089a6..7fec503c9423 100644 --- a/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart +++ b/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart @@ -2,5 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +export 'src/events/camera_event.dart'; export 'src/platform_interface/camera_platform.dart'; export 'src/types/types.dart'; 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 new file mode 100644 index 000000000000..e5d603b1b9b4 --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart @@ -0,0 +1,62 @@ +import 'dart:ui'; + +/// Generic Event coming from the native side of Camera. +/// +/// All [CameraEvent]s contain the `cameraId` that originated the event. This +/// should never be `null`. +/// +/// This class is used as a base class for all the events that might be +/// triggered from a Camera, but it is never used directly as an event type. +/// +/// Do NOT instantiate new events like `CameraEvent(cameraId)` directly, +/// use a specific class instead: +/// +/// Do `class NewEvent extend CameraEvent` when creating your own events. +/// See below for examples: `CameraClosingEvent`, `CameraErrorEvent`... +/// These events are more semantic and more pleasant to use than raw generics. +/// They can be (and in fact, are) filtered by the `instanceof`-operator. +abstract class CameraEvent { + /// The ID of the Camera this event is associated to. + final int cameraId; + + /// Build a Camera Event, that relates a `cameraId`. + /// + /// The `cameraId` is the ID of the camera that triggered the event. + CameraEvent(this.cameraId); +} + +/// An event fired when the resolution preset of the camera has changed. +class ResolutionChangedEvent extends CameraEvent { + /// The capture size in pixels. + final Size captureSize; + + /// The size of the preview in pixels. + final Size previewSize; + + /// Build a ResolutionChanged event triggered from the camera represented by + /// `cameraId`. + /// + /// The `captureSize` represents the size of the resulting image in pixels. + /// The `previewSize` represents the size of the generated preview in pixels. + ResolutionChangedEvent(int cameraId, this.captureSize, this.previewSize) + : super(cameraId); +} + +/// An event fired when the camera is going to close. +class CameraClosingEvent extends CameraEvent { + /// Build a CameraClosing event triggered from the camera represented by + /// `cameraId`. + CameraClosingEvent(int cameraId) : super(cameraId); +} + +/// An event fired when an error occured while operating the camera. +class CameraErrorEvent extends CameraEvent { + /// Description of the error. + final String description; + + /// Build a CameraError event triggered from the camera represented by + /// `cameraId`. + /// + /// The `description` represents the error occured on the camera. + CameraErrorEvent(int cameraId, this.description) : super(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 4e3a6d8cb3f9..af200e682777 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 @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:flutter/widgets.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; @@ -41,7 +42,7 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('availableCameras() is not implemented.'); } - /// Initializes the camera on the device and returns its textureId. + /// Initializes the camera on the device and returns its cameraId. Future initializeCamera( CameraDescription cameraDescription, { ResolutionPreset resolutionPreset, @@ -50,13 +51,23 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('initializeCamera() is not implemented.'); } - /// Returns a Stream of [CameraEvent]s. - Stream cameraEventsFor(int textureId) { - throw UnimplementedError('videoEventsFor() has not been implemented.'); + /// The camera's resolution has changed + Stream onResolutionChanged(int cameraId) { + throw UnimplementedError('onResolutionChanged() is not implemented.'); + } + + /// The camera started to close. + Stream onCameraClosing(int cameraId) { + throw UnimplementedError('onCameraClosing() is not implemented.'); + } + + /// The camera experienced an error. + Stream onCameraError(int cameraId) { + throw UnimplementedError('onCameraError() is not implemented.'); } /// Captures an image and saves it to [path]. - Future takePicture(int textureId, String path) { + Future takePicture(int cameraId, String path) { throw UnimplementedError('takePicture() is not implemented.'); } @@ -73,43 +84,32 @@ abstract class CameraPlatform extends PlatformInterface { /// The file is written on the flight as the video is being recorded. /// If a file already exists at the provided path an error will be thrown. /// The file can be read as soon as [stopVideoRecording] returns. - Future startVideoRecording(int textureId, String path) { + Future startVideoRecording(int cameraId, String path) { throw UnimplementedError('startVideoRecording() is not implemented.'); } /// Stop the video recording. - Future stopVideoRecording(int textureId) { + Future stopVideoRecording(int cameraId) { throw UnimplementedError('stopVideoRecording() is not implemented.'); } /// Pause video recording. - Future pauseVideoRecording(int textureId) { + Future pauseVideoRecording(int cameraId) { throw UnimplementedError('pauseVideoRecording() is not implemented.'); } /// Resume video recording after pausing. - Future resumeVideoRecording(int textureId) { + Future resumeVideoRecording(int cameraId) { throw UnimplementedError('resumeVideoRecording() is not implemented.'); } - /// Start streaming images from platform camera. - /// - /// When running continuously with [CameraPreview] widget, this function runs - /// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can - /// have significant frame rate drops for [CameraPreview] on lower end - /// devices. - // TODO(bmparr): Add settings for resolution and fps. - Future startImageStream(ImageAvailableHandler onAvailable) { - throw UnimplementedError('startImageStream() is not implemented.'); - } - - /// Stop streaming images from platform camera. - Future stopImageStream() { - throw UnimplementedError('stopImageStream() is not implemented.'); + /// Returns a widget showing a live camera preview. + Widget buildView(int cameraId) { + throw UnimplementedError('buildView() has not been implemented.'); } /// Releases the resources of this camera. - Future dispose(int textureId) { + Future dispose(int cameraId) { throw UnimplementedError('dispose() is not implemented.'); } } diff --git a/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart b/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart deleted file mode 100644 index 8cf3a00bc245..000000000000 --- a/packages/camera/camera_platform_interface/lib/src/types/callbacks.dart +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'types.dart'; - -/// Signature for a callback receiving the a camera image. -/// -/// This is used by [CameraPlatform.startImageStream]. -// ignore: inference_failure_on_function_return_type -typedef ImageAvailableHandler = Function(CameraImage image); diff --git a/packages/camera/camera_platform_interface/lib/src/types/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/types/camera_event.dart deleted file mode 100644 index 7b9b47381e28..000000000000 --- a/packages/camera/camera_platform_interface/lib/src/types/camera_event.dart +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:ui'; - -import 'package:meta/meta.dart'; - -/// Event emitted from the platform implementation. -class CameraEvent { - /// Creates an instance of [CameraEvent]. - /// - /// The [eventType] argument is required. - /// - /// Depending on the [eventType], the [captureSize], [previewSize] and - /// [errorDescription] arguments can be null. - CameraEvent({ - @required this.eventType, - this.captureSize, - this.previewSize, - this.errorDescription, - }); - - /// The type of the event. - final CameraEventType eventType; - - /// The capture size in pixels. - /// - /// Only used if the [eventType] is [CameraEventType.initialized] - final Size captureSize; - - /// The size of the preview in pixels. - /// - /// Only used if the [eventType] is [CameraEventType.initialized] - final Size previewSize; - - /// Description of the error. - /// - /// Only used if [eventType] is [CameraEventType.error]. - final String errorDescription; - - @override - bool operator ==(Object other) { - return identical(this, other) || - other is CameraEvent && - runtimeType == other.runtimeType && - eventType == other.eventType && - captureSize == other.captureSize && - previewSize == other.previewSize && - errorDescription == other.errorDescription; - } - - @override - int get hashCode => - eventType.hashCode ^ - captureSize.hashCode ^ - previewSize.hashCode ^ - errorDescription.hashCode; -} - -/// Type of the event. -/// -/// Emitted by the platform implementation when the camera is closing or -/// if an error occured. -enum CameraEventType { - /// The camera has been initialized. - - /// The camera is closing. - cameraClosing, - - /// An error occured while accessing the camera. - error, - - /// An unknown event has been received. - unknown, -} diff --git a/packages/camera/camera_platform_interface/lib/src/types/types.dart b/packages/camera/camera_platform_interface/lib/src/types/types.dart index faa0f554526a..616c4394d7b9 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/types.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/types.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -export 'callbacks.dart'; export 'camera_description.dart'; -export 'camera_event.dart'; export 'camera_image.dart'; export 'resolution_preset.dart'; diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index f3cd2c9f03c2..fff072ff1f1f 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -45,117 +45,118 @@ void main() { }); test( - 'Default implementation of cameraEventsFor() should throw unimplemented error', + 'Default implementation of onResolutionChanged() should throw unimplemented error', () { // Arrange final cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( - () => cameraPlatform.cameraEventsFor(1), + () => cameraPlatform.onResolutionChanged(1), throwsUnimplementedError, ); }); - test('Default implementation of dispose() should throw unimplemented error', + + test( + 'Default implementation of onCameraClosing() should throw unimplemented error', () { // Arrange final cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( - () => cameraPlatform.dispose(1), + () => cameraPlatform.onCameraClosing(1), throwsUnimplementedError, ); }); test( - 'Default implementation of initialize() should throw unimplemented error', + 'Default implementation of onCameraError() should throw unimplemented error', () { // Arrange final cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( - () => cameraPlatform.initializeCamera(null), + () => cameraPlatform.onCameraError(1), throwsUnimplementedError, ); }); - test( - 'Default implementation of pauseVideoRecording() should throw unimplemented error', + test('Default implementation of dispose() should throw unimplemented error', () { // Arrange final cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( - () => cameraPlatform.pauseVideoRecording(1), + () => cameraPlatform.dispose(1), throwsUnimplementedError, ); }); test( - 'Default implementation of prepareForVideoRecording() should throw unimplemented error', + 'Default implementation of initialize() should throw unimplemented error', () { // Arrange final cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( - () => cameraPlatform.prepareForVideoRecording(), + () => cameraPlatform.initializeCamera(null), throwsUnimplementedError, ); }); test( - 'Default implementation of resumeVideoRecording() should throw unimplemented error', + 'Default implementation of pauseVideoRecording() should throw unimplemented error', () { // Arrange final cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( - () => cameraPlatform.resumeVideoRecording(1), + () => cameraPlatform.pauseVideoRecording(1), throwsUnimplementedError, ); }); test( - 'Default implementation of startImageStream() should throw unimplemented error', + 'Default implementation of prepareForVideoRecording() should throw unimplemented error', () { // Arrange final cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( - () => cameraPlatform.startImageStream(null), + () => cameraPlatform.prepareForVideoRecording(), throwsUnimplementedError, ); }); test( - 'Default implementation of startVideoRecording() should throw unimplemented error', + 'Default implementation of resumeVideoRecording() should throw unimplemented error', () { // Arrange final cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( - () => cameraPlatform.startVideoRecording(1, null), + () => cameraPlatform.resumeVideoRecording(1), throwsUnimplementedError, ); }); test( - 'Default implementation of stopImageStream() should throw unimplemented error', + 'Default implementation of startVideoRecording() should throw unimplemented error', () { // Arrange final cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( - () => cameraPlatform.stopImageStream(), + () => cameraPlatform.startVideoRecording(1, null), throwsUnimplementedError, ); }); From 613a92b6682915f2942e559c47789c4ff140e8c9 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 12 Nov 2020 12:51:05 +0100 Subject: [PATCH 09/89] Updated platform interface to have recording methods return XFile instances. --- .../lib/src/platform_interface/camera_platform.dart | 7 ++++--- packages/camera/camera_platform_interface/pubspec.yaml | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) 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 af200e682777..634a052da832 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 @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/widgets.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; @@ -67,7 +68,7 @@ abstract class CameraPlatform extends PlatformInterface { } /// Captures an image and saves it to [path]. - Future takePicture(int cameraId, String path) { + Future takePicture(int cameraId) { throw UnimplementedError('takePicture() is not implemented.'); } @@ -84,12 +85,12 @@ abstract class CameraPlatform extends PlatformInterface { /// The file is written on the flight as the video is being recorded. /// If a file already exists at the provided path an error will be thrown. /// The file can be read as soon as [stopVideoRecording] returns. - Future startVideoRecording(int cameraId, String path) { + Future startVideoRecording(int cameraId) { throw UnimplementedError('startVideoRecording() is not implemented.'); } /// Stop the video recording. - Future stopVideoRecording(int cameraId) { + Future stopVideoRecording(int cameraId) { throw UnimplementedError('stopVideoRecording() is not implemented.'); } diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 4d6f4733927c..1316e35f0706 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -10,6 +10,7 @@ dependencies: sdk: flutter meta: ^1.0.5 plugin_platform_interface: ^1.0.1 + file_selector_platform_interface: ^1.0.0 dev_dependencies: flutter_test: From 247d9c9f016f8a1be97fa126042a5550a1d07455 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 12 Nov 2020 13:02:50 +0100 Subject: [PATCH 10/89] Update documentation and unit tests to match platform interface changes --- .../lib/src/platform_interface/camera_platform.dart | 11 ++++------- .../test/camera_platform_interface_test.dart | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) 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 634a052da832..0a8615cba4b0 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 @@ -67,7 +67,7 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('onCameraError() is not implemented.'); } - /// Captures an image and saves it to [path]. + /// Captures an image and returns the file where it was saved Future takePicture(int cameraId) { throw UnimplementedError('takePicture() is not implemented.'); } @@ -77,19 +77,16 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('prepareForVideoRecording() is not implemented.'); } - /// Start a video recording and save the file to [path]. - /// - /// A path can for example be obtained using - /// [path_provider](https://pub.dartlang.org/packages/path_provider). + /// Starts a video recording /// /// The file is written on the flight as the video is being recorded. /// If a file already exists at the provided path an error will be thrown. - /// The file can be read as soon as [stopVideoRecording] returns. + /// The file can be read as soon as [stopVideoRecording] returns it. Future startVideoRecording(int cameraId) { throw UnimplementedError('startVideoRecording() is not implemented.'); } - /// Stop the video recording. + /// Stops the video recording and returns the file where it was saved Future stopVideoRecording(int cameraId) { throw UnimplementedError('stopVideoRecording() is not implemented.'); } diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index fff072ff1f1f..bccdf3a86abd 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -156,7 +156,7 @@ void main() { // Act & Assert expect( - () => cameraPlatform.startVideoRecording(1, null), + () => cameraPlatform.startVideoRecording(1), throwsUnimplementedError, ); }); @@ -182,7 +182,7 @@ void main() { // Act & Assert expect( - () => cameraPlatform.takePicture(1, null), + () => cameraPlatform.takePicture(1), throwsUnimplementedError, ); }); From 51c25b57f991aa31ac59d1c7e92dacbf51e1fa93 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 12 Nov 2020 13:08:41 +0100 Subject: [PATCH 11/89] Make file input optional for recording methods in platform interface. Update docs. --- .../lib/src/platform_interface/camera_platform.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 0a8615cba4b0..b62439cbfa52 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 @@ -67,8 +67,9 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('onCameraError() is not implemented.'); } - /// Captures an image and returns the file where it was saved - Future takePicture(int cameraId) { + /// Captures an image and returns the file where it was saved. + /// If no [file] parameter is provided, the file returned will be in a temporary location. + Future takePicture(int cameraId, { XFile file }) { throw UnimplementedError('takePicture() is not implemented.'); } @@ -79,10 +80,12 @@ abstract class CameraPlatform extends PlatformInterface { /// Starts a video recording /// + /// If no [file] parameter is provided, the recording will be saved to a new file in a temporary location. + /// /// The file is written on the flight as the video is being recorded. - /// If a file already exists at the provided path an error will be thrown. + /// If a file already exists at the path for the provided file instance, an error will be thrown. /// The file can be read as soon as [stopVideoRecording] returns it. - Future startVideoRecording(int cameraId) { + Future startVideoRecording(int cameraId, { XFile file }) { throw UnimplementedError('startVideoRecording() is not implemented.'); } From 1b0632296db0cfe7a245cfbeae44f19e78806149 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 12 Nov 2020 13:10:46 +0100 Subject: [PATCH 12/89] Add missing full stop in docs. --- .../lib/src/platform_interface/camera_platform.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b62439cbfa52..7c4c7ecf3856 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 @@ -81,7 +81,7 @@ abstract class CameraPlatform extends PlatformInterface { /// Starts a video recording /// /// If no [file] parameter is provided, the recording will be saved to a new file in a temporary location. - /// + /// /// The file is written on the flight as the video is being recorded. /// If a file already exists at the path for the provided file instance, an error will be thrown. /// The file can be read as soon as [stopVideoRecording] returns it. @@ -89,7 +89,7 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('startVideoRecording() is not implemented.'); } - /// Stops the video recording and returns the file where it was saved + /// Stops the video recording and returns the file where it was saved. Future stopVideoRecording(int cameraId) { throw UnimplementedError('stopVideoRecording() is not implemented.'); } From 8e94057e28878acdb0b8000ea88d29c9a7ae247b Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 12 Nov 2020 13:52:08 +0100 Subject: [PATCH 13/89] Run dartfmt. Wrapped docs after max 80 cols. Added missing full stop. --- .../src/platform_interface/camera_platform.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) 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 7c4c7ecf3856..9b486a509984 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 @@ -4,10 +4,10 @@ import 'dart:async'; +import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/widgets.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; import '../../camera_platform_interface.dart'; @@ -68,8 +68,9 @@ abstract class CameraPlatform extends PlatformInterface { } /// Captures an image and returns the file where it was saved. - /// If no [file] parameter is provided, the file returned will be in a temporary location. - Future takePicture(int cameraId, { XFile file }) { + /// If no [file] parameter is provided, the file returned will be in a + /// temporary location. + Future takePicture(int cameraId, {XFile file}) { throw UnimplementedError('takePicture() is not implemented.'); } @@ -78,14 +79,16 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('prepareForVideoRecording() is not implemented.'); } - /// Starts a video recording + /// Starts a video recording. /// - /// If no [file] parameter is provided, the recording will be saved to a new file in a temporary location. + /// If no [file] parameter is provided, the recording will be saved to a new + /// file in a temporary location. /// /// The file is written on the flight as the video is being recorded. - /// If a file already exists at the path for the provided file instance, an error will be thrown. + /// If a file already exists at the path for the provided file instance, + /// an error will be thrown. /// The file can be read as soon as [stopVideoRecording] returns it. - Future startVideoRecording(int cameraId, { XFile file }) { + Future startVideoRecording(int cameraId, {XFile file}) { throw UnimplementedError('startVideoRecording() is not implemented.'); } From 05a8f832ec3c1fbabe1f3e6d2b4fe9406b1858ed Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 11:26:40 +0100 Subject: [PATCH 14/89] Implemented & tested first parts of method channel implementation --- .../lib/src/events/camera_event.dart | 91 +++++++++++-- .../method_channel/method_channel_camera.dart | 120 ++++++++++++++++++ .../platform_interface/camera_platform.dart | 4 +- .../lib/src/types/camera_description.dart | 17 +-- .../lib/src/types/camera_exception.dart | 20 +++ .../lib/src/types/types.dart | 1 + .../camera_platform_interface/pubspec.yaml | 2 + .../test/camera_platform_interface_test.dart | 2 +- .../test/utils/event_channel_mock.dart | 78 ++++++++++++ .../test/utils/method_channel_mock.dart | 34 +++++ 10 files changed, 348 insertions(+), 21 deletions(-) create mode 100644 packages/camera/camera_platform_interface/lib/src/types/camera_exception.dart create mode 100644 packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart create mode 100644 packages/camera/camera_platform_interface/test/utils/method_channel_mock.dart 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 e5d603b1b9b4..d42523941c3f 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 @@ -1,5 +1,3 @@ -import 'dart:ui'; - /// Generic Event coming from the native side of Camera. /// /// All [CameraEvent]s contain the `cameraId` that originated the event. This @@ -27,19 +25,62 @@ abstract class CameraEvent { /// An event fired when the resolution preset of the camera has changed. class ResolutionChangedEvent extends CameraEvent { - /// The capture size in pixels. - final Size captureSize; + /// The capture width in pixels. + final int captureWidth; + + /// The capture height in pixels. + final int captureHeight; - /// The size of the preview in pixels. - final Size previewSize; + /// The width of the preview in pixels. + final int previewWidth; + + /// The height of the preview in pixels. + final int previewHeight; /// Build a ResolutionChanged event triggered from the camera represented by /// `cameraId`. /// /// The `captureSize` represents the size of the resulting image in pixels. /// The `previewSize` represents the size of the generated preview in pixels. - ResolutionChangedEvent(int cameraId, this.captureSize, this.previewSize) - : super(cameraId); + ResolutionChangedEvent( + int cameraId, + this.captureWidth, + this.captureHeight, + this.previewWidth, + this.previewHeight, + ) : super(cameraId); + + ResolutionChangedEvent.fromJson(Map json) + : captureWidth = json['captureWidth'], + captureHeight = json['captureHeight'], + previewWidth = json['previewWidth'], + previewHeight = json['previewHeight'], + super(json['cameraId']); + + Map toJson() => { + 'cameraId': cameraId, + 'captureWidth': captureWidth, + 'captureHeight': captureHeight, + 'previewWidth': previewWidth, + 'previewHeight': previewHeight, + }; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ResolutionChangedEvent && + runtimeType == other.runtimeType && + captureWidth == other.captureWidth && + captureHeight == other.captureHeight && + previewWidth == other.previewWidth && + previewHeight == other.previewHeight; + + @override + int get hashCode => + captureWidth.hashCode ^ + captureHeight.hashCode ^ + previewWidth.hashCode ^ + previewHeight.hashCode; } /// An event fired when the camera is going to close. @@ -47,6 +88,21 @@ class CameraClosingEvent extends CameraEvent { /// Build a CameraClosing event triggered from the camera represented by /// `cameraId`. CameraClosingEvent(int cameraId) : super(cameraId); + + CameraClosingEvent.fromJson(Map json) + : super(json['cameraId']); + + Map toJson() => { + 'cameraId': cameraId, + }; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is CameraClosingEvent && runtimeType == other.runtimeType; + + @override + int get hashCode => 0; } /// An event fired when an error occured while operating the camera. @@ -59,4 +115,23 @@ class CameraErrorEvent extends CameraEvent { /// /// The `description` represents the error occured on the camera. CameraErrorEvent(int cameraId, this.description) : super(cameraId); + + CameraErrorEvent.fromJson(Map json) + : description = json['description'], + super(json['cameraId']); + + Map toJson() => { + 'cameraId': cameraId, + 'description': description, + }; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is CameraErrorEvent && + runtimeType == other.runtimeType && + description == other.description; + + @override + int get hashCode => description.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 6342224bd9ea..4c90525a3cbb 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 @@ -2,11 +2,131 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart'; +import 'package:stream_transform/stream_transform.dart'; + +const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); /// An implementation of [CameraPlatform] that uses method channels. class MethodChannelCamera extends CameraPlatform { // TODO(mvanbeusekom): Add default method channel implementation of the // [CameraPlatform] interface. + final Map _channels = {}; + final StreamController _cameraEventStreamController = + StreamController.broadcast(); + + Stream _events(int cameraId) => + _cameraEventStreamController.stream + .where((event) => event.cameraId == cameraId); + + @override + Future initializeCamera( + CameraDescription cameraDescription, + ResolutionPreset resolutionPreset, { + bool enableAudio, + }) async { + int _textureId; + try { + final Map reply = + await _channel.invokeMapMethod( + 'initialize', + { + 'cameraName': cameraDescription.name, + 'resolutionPreset': resolutionPreset != null + ? _serializeResolutionPreset(resolutionPreset) + : null, + 'enableAudio': enableAudio, + }, + ); + _textureId = reply['textureId']; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + if (!_channels.containsKey(_textureId)) { + final channel = + MethodChannel('flutter.io/cameraPlugin/camera$_textureId'); + channel.setMethodCallHandler( + (MethodCall call) => handleMethodCall(call, _textureId)); + _channels[_textureId] = channel; + } + return _textureId; + } + + @override + Future dispose(int cameraId) async { + await _channel.invokeMethod( + 'dispose', + {'textureId': cameraId}, + ); + _channels.remove(cameraId); + } + + @override + Stream onResolutionChanged(int cameraId) { + assert(_channels.containsKey(cameraId)); + return _events(cameraId).whereType(); + } + + @override + Stream onCameraClosing(int cameraId) { + assert(_channels.containsKey(cameraId)); + return _events(cameraId).whereType(); + } + + @override + Stream onCameraError(int cameraId) { + assert(_channels.containsKey(cameraId)); + return _events(cameraId).whereType(); + } + + /// Returns the resolution preset as a String. + String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { + switch (resolutionPreset) { + case ResolutionPreset.max: + return 'max'; + case ResolutionPreset.ultraHigh: + return 'ultraHigh'; + case ResolutionPreset.veryHigh: + return 'veryHigh'; + case ResolutionPreset.high: + return 'high'; + case ResolutionPreset.medium: + return 'medium'; + case ResolutionPreset.low: + return 'low'; + } + throw ArgumentError('Unknown ResolutionPreset value'); + } + @visibleForTesting + Future handleMethodCall(MethodCall call, int cameraId) async { + switch (call.method) { + case 'camera#resolutionChanged': + _cameraEventStreamController.add(ResolutionChangedEvent( + cameraId, + call.arguments['captureWidth'], + call.arguments['captureHeight'], + call.arguments['previewWidth'], + call.arguments['previewHeight'], + )); + break; + case 'camera#closing': + _cameraEventStreamController.add(CameraClosingEvent( + cameraId, + )); + break; + case 'camera#error': + _cameraEventStreamController.add(CameraErrorEvent( + cameraId, + call.arguments['description'], + )); + break; + default: + throw MissingPluginException(); + } + } } 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 af200e682777..6665771084ab 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 @@ -44,8 +44,8 @@ abstract class CameraPlatform extends PlatformInterface { /// Initializes the camera on the device and returns its cameraId. Future initializeCamera( - CameraDescription cameraDescription, { - ResolutionPreset resolutionPreset, + CameraDescription cameraDescription, + ResolutionPreset resolutionPreset, { bool enableAudio, }) { throw UnimplementedError('initializeCamera() is not implemented.'); diff --git a/packages/camera/camera_platform_interface/lib/src/types/camera_description.dart b/packages/camera/camera_platform_interface/lib/src/types/camera_description.dart index 7a11e921b5f6..c19af1f50d1c 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/camera_description.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/camera_description.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui'; - /// The direction the camera is facing. enum CameraLensDirection { /// Front facing camera (a user looking at the screen is seen by the camera). @@ -37,16 +35,15 @@ class CameraDescription { final int sensorOrientation; @override - bool operator ==(Object o) { - return o is CameraDescription && - o.name == name && - o.lensDirection == lensDirection; - } + bool operator ==(Object other) => + identical(this, other) || + other is CameraDescription && + runtimeType == other.runtimeType && + name == other.name && + lensDirection == other.lensDirection; @override - int get hashCode { - return hashValues(name, lensDirection); - } + int get hashCode => name.hashCode ^ lensDirection.hashCode; @override String toString() { diff --git a/packages/camera/camera_platform_interface/lib/src/types/camera_exception.dart b/packages/camera/camera_platform_interface/lib/src/types/camera_exception.dart new file mode 100644 index 000000000000..00fb36eedb9f --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/camera_exception.dart @@ -0,0 +1,20 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// This is thrown when the plugin reports an error. +class CameraException implements Exception { + /// Creates a new camera exception with the given error code and description. + CameraException(this.code, this.description); + + /// Error code. + // TODO(bparrishMines): Document possible error codes. + // https://github.com/flutter/flutter/issues/69298 + String code; + + /// Textual description of the error. + String description; + + @override + String toString() => '$runtimeType($code, $description)'; +} \ No newline at end of file diff --git a/packages/camera/camera_platform_interface/lib/src/types/types.dart b/packages/camera/camera_platform_interface/lib/src/types/types.dart index 616c4394d7b9..b3eab45ff7ee 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/types.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/types.dart @@ -5,3 +5,4 @@ export 'camera_description.dart'; export 'camera_image.dart'; export 'resolution_preset.dart'; +export 'camera_exception.dart'; \ No newline at end of file diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 4d6f4733927c..4c393f8c4ccf 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -10,6 +10,8 @@ dependencies: sdk: flutter meta: ^1.0.5 plugin_platform_interface: ^1.0.1 + file_selector_platform_interface: ^1.0.0 + stream_transform: ^1.2.0 dev_dependencies: flutter_test: diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index fff072ff1f1f..91fa469f5402 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -104,7 +104,7 @@ void main() { // Act & Assert expect( - () => cameraPlatform.initializeCamera(null), + () => cameraPlatform.initializeCamera(null, null), throwsUnimplementedError, ); }); diff --git a/packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart b/packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart new file mode 100644 index 000000000000..4bef6379ff80 --- /dev/null +++ b/packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart @@ -0,0 +1,78 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart'; + +class EventChannelMock { + final MethodChannel _methodChannel; + final Stream stream; + final log = []; + + StreamSubscription _streamSubscription; + + EventChannelMock({ + @required String channelName, + @required this.stream, + }) : _methodChannel = MethodChannel(channelName) { + _methodChannel.setMockMethodCallHandler(_handler); + } + + Future _handler(MethodCall methodCall) { + log.add(methodCall); + + switch (methodCall.method) { + case 'listen': + _onListen(); + break; + case 'cancel': + _onCancel(); + break; + default: + return null; + } + + return Future.value(); + } + + void _onListen() { + _streamSubscription = stream.handleError((error) { + ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + _methodChannel.name, + _createErrorEnvelope(error), + (_) {}, + ); + }).listen((event) { + ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + _methodChannel.name, + _createSuccessEnvelope(event), + (_) {}, + ); + }); + } + + void _onCancel() { + if (_streamSubscription != null) { + _streamSubscription.cancel(); + } + } + + ByteData _createErrorEnvelope(Exception error) { + var code = "UNKNOWN_EXCEPTION"; + String message; + dynamic details; + + if (error is PlatformException) { + code = error.code; + message = error.message; + details = error.details; + } + + return const StandardMethodCodec() + .encodeErrorEnvelope(code: code, message: message, details: details); + } + + ByteData _createSuccessEnvelope(dynamic event) { + return const StandardMethodCodec().encodeSuccessEnvelope(event); + } +} \ No newline at end of file diff --git a/packages/camera/camera_platform_interface/test/utils/method_channel_mock.dart b/packages/camera/camera_platform_interface/test/utils/method_channel_mock.dart new file mode 100644 index 000000000000..482ec6fcfbd7 --- /dev/null +++ b/packages/camera/camera_platform_interface/test/utils/method_channel_mock.dart @@ -0,0 +1,34 @@ +import 'package:flutter/services.dart'; + +class MethodChannelMock { + final Duration delay; + final MethodChannel methodChannel; + final Map methods; + final log = []; + + MethodChannelMock({ + String channelName, + this.delay, + this.methods, + }) : methodChannel = MethodChannel(channelName) { + methodChannel.setMockMethodCallHandler(_handler); + } + + Future _handler(MethodCall methodCall) async { + log.add(methodCall); + + if (!methods.containsKey(methodCall.method)) { + throw MissingPluginException('No implementation found for method ' + '${methodCall.method} on channel ${methodChannel.name}'); + } + + return Future.delayed(delay ?? Duration.zero, () { + final result = methods[methodCall.method]; + if (result is Exception) { + throw result; + } + + return Future.value(result); + }); + } +} From 5206bffefa822c7abc8f1a303b95af935519d9df Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 11:29:36 +0100 Subject: [PATCH 15/89] Remove unused EventChannelMock class --- .../test/utils/event_channel_mock.dart | 78 ------------------- 1 file changed, 78 deletions(-) delete mode 100644 packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart diff --git a/packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart b/packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart deleted file mode 100644 index 4bef6379ff80..000000000000 --- a/packages/camera/camera_platform_interface/test/utils/event_channel_mock.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/services.dart'; -import 'package:meta/meta.dart'; - -class EventChannelMock { - final MethodChannel _methodChannel; - final Stream stream; - final log = []; - - StreamSubscription _streamSubscription; - - EventChannelMock({ - @required String channelName, - @required this.stream, - }) : _methodChannel = MethodChannel(channelName) { - _methodChannel.setMockMethodCallHandler(_handler); - } - - Future _handler(MethodCall methodCall) { - log.add(methodCall); - - switch (methodCall.method) { - case 'listen': - _onListen(); - break; - case 'cancel': - _onCancel(); - break; - default: - return null; - } - - return Future.value(); - } - - void _onListen() { - _streamSubscription = stream.handleError((error) { - ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( - _methodChannel.name, - _createErrorEnvelope(error), - (_) {}, - ); - }).listen((event) { - ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( - _methodChannel.name, - _createSuccessEnvelope(event), - (_) {}, - ); - }); - } - - void _onCancel() { - if (_streamSubscription != null) { - _streamSubscription.cancel(); - } - } - - ByteData _createErrorEnvelope(Exception error) { - var code = "UNKNOWN_EXCEPTION"; - String message; - dynamic details; - - if (error is PlatformException) { - code = error.code; - message = error.message; - details = error.details; - } - - return const StandardMethodCodec() - .encodeErrorEnvelope(code: code, message: message, details: details); - } - - ByteData _createSuccessEnvelope(dynamic event) { - return const StandardMethodCodec().encodeSuccessEnvelope(event); - } -} \ No newline at end of file From 492fae030424515933c52ed4afa55a8d60036551 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 11:32:58 +0100 Subject: [PATCH 16/89] Add missing unit tests --- .../test/method_channel_camera_test.dart | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 packages/camera/camera_platform_interface/test/method_channel_camera_test.dart diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart new file mode 100644 index 000000000000..49f037c36c7b --- /dev/null +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -0,0 +1,180 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:async/async.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils/method_channel_mock.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$MethodChannelCamera', () { + group('Initialization & Disposal Tests', () { + test('Should receive a camera id when initialized', () async { + // Arrange + MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'initialize': {'textureId': 1} + }); + final camera = MethodChannelCamera(); + + // Act + final cameraId = await camera.initializeCamera( + CameraDescription(name: 'Test'), + ResolutionPreset.high, + ); + print('cameraId $cameraId'); + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + isMethodCall( + 'initialize', + arguments: { + 'cameraName': 'Test', + 'resolutionPreset': 'high', + 'enableAudio': null + }, + ), + ]); + }); + + test('Should send a disposal call on dispose', () async { + // Arrange + MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'initialize': {'textureId': 1}, + 'dispose': {'textureId': 1} + }); + + final camera = MethodChannelCamera(); + final cameraId = await camera.initializeCamera( + CameraDescription(name: 'Test'), + ResolutionPreset.high, + ); + + // Act + await camera.dispose(cameraId); + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + isNotNull, + isMethodCall( + 'dispose', + arguments: {'textureId': 1}, + ), + ]); + }); + }); + + group('Interaction Tests', () { + MethodChannelMock channel; + MethodChannelCamera camera; + int cameraId; + setUp(() async { + channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'initialize': {'textureId': 1}, + }, + ); + camera = MethodChannelCamera(); + cameraId = await camera.initializeCamera( + CameraDescription(name: 'Test'), + ResolutionPreset.high, + ); + }); + + test('Should receive resolution changes', () async { + // Act + final Stream resolutionStream = + camera.onResolutionChanged(cameraId); + final streamQueue = StreamQueue(resolutionStream); + + // Emit test events + final fhdEvent = + ResolutionChangedEvent(cameraId, 1920, 1080, 1280, 720); + final uhdEvent = + ResolutionChangedEvent(cameraId, 3840, 2160, 1280, 720); + await camera.handleMethodCall( + MethodCall('camera#resolutionChanged', fhdEvent.toJson()), + cameraId); + await camera.handleMethodCall( + MethodCall('camera#resolutionChanged', uhdEvent.toJson()), + cameraId); + await camera.handleMethodCall( + MethodCall('camera#resolutionChanged', fhdEvent.toJson()), + cameraId); + await camera.handleMethodCall( + MethodCall('camera#resolutionChanged', uhdEvent.toJson()), + cameraId); + + // Assert + expect(await streamQueue.next, fhdEvent); + expect(await streamQueue.next, uhdEvent); + expect(await streamQueue.next, fhdEvent); + expect(await streamQueue.next, uhdEvent); + // + // // Clean up + await streamQueue.cancel(); + }); + + test('Should receive camera closing events', () async { + // Act + final Stream eventStream = + camera.onCameraClosing(cameraId); + final streamQueue = StreamQueue(eventStream); + + // Emit test events + final event = CameraClosingEvent(cameraId); + await camera.handleMethodCall( + MethodCall('camera#closing', event.toJson()), cameraId); + await camera.handleMethodCall( + MethodCall('camera#closing', event.toJson()), cameraId); + await camera.handleMethodCall( + MethodCall('camera#closing', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + + test('Should receive camera error events', () async { + // Act + final errorStream = camera.onCameraError(cameraId); + final streamQueue = StreamQueue(errorStream); + + // Emit test events + final event = CameraErrorEvent(cameraId, 'Error Description'); + await camera.handleMethodCall( + MethodCall('camera#error', event.toJson()), cameraId); + await camera.handleMethodCall( + MethodCall('camera#error', event.toJson()), cameraId); + await camera.handleMethodCall( + MethodCall('camera#error', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); + }); + }); + }); +} From 6b381ef563f0813b48250a68a9db11f89cdc293e Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 13:52:40 +0100 Subject: [PATCH 17/89] Add availableCameras to method channel implementation --- .../method_channel/method_channel_camera.dart | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) 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 4c90525a3cbb..5d5ba4a401d9 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 @@ -23,6 +23,23 @@ class MethodChannelCamera extends CameraPlatform { _cameraEventStreamController.stream .where((event) => event.cameraId == cameraId); + @override + Future> availableCameras() async { + try { + final List> cameras = await _channel + .invokeListMethod>('availableCameras'); + return cameras.map((Map camera) { + return CameraDescription( + name: camera['name'], + lensDirection: _parseCameraLensDirection(camera['lensFacing']), + sensorOrientation: camera['sensorOrientation'], + ); + }).toList(); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + @override Future initializeCamera( CameraDescription cameraDescription, @@ -102,6 +119,19 @@ class MethodChannelCamera extends CameraPlatform { throw ArgumentError('Unknown ResolutionPreset value'); } + // Parses a string into a corresponding CameraLensDirection. + CameraLensDirection _parseCameraLensDirection(String string) { + switch (string) { + case 'front': + return CameraLensDirection.front; + case 'back': + return CameraLensDirection.back; + case 'external': + return CameraLensDirection.external; + } + throw ArgumentError('Unknown CameraLensDirection value'); + } + @visibleForTesting Future handleMethodCall(MethodCall call, int cameraId) async { switch (call.method) { From 96bccd422e7b74561e83bc5180b231ee42c4ed68 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 13:54:06 +0100 Subject: [PATCH 18/89] Updated platform interface --- .../src/platform_interface/camera_platform.dart | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) 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 9b486a509984..243fdfa75879 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 @@ -68,9 +68,7 @@ abstract class CameraPlatform extends PlatformInterface { } /// Captures an image and returns the file where it was saved. - /// If no [file] parameter is provided, the file returned will be in a - /// temporary location. - Future takePicture(int cameraId, {XFile file}) { + Future takePicture(int cameraId) { throw UnimplementedError('takePicture() is not implemented.'); } @@ -79,21 +77,16 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('prepareForVideoRecording() is not implemented.'); } - /// Starts a video recording. - /// - /// If no [file] parameter is provided, the recording will be saved to a new - /// file in a temporary location. + /// Starts a video recording and returns the file where it will be saved. /// /// The file is written on the flight as the video is being recorded. - /// If a file already exists at the path for the provided file instance, - /// an error will be thrown. /// The file can be read as soon as [stopVideoRecording] returns it. - Future startVideoRecording(int cameraId, {XFile file}) { + Future startVideoRecording(int cameraId) { throw UnimplementedError('startVideoRecording() is not implemented.'); } - /// Stops the video recording and returns the file where it was saved. - Future stopVideoRecording(int cameraId) { + /// Stops the video recording. + Future stopVideoRecording(int cameraId) { throw UnimplementedError('stopVideoRecording() is not implemented.'); } From 79b58c81ba13d018a03559b0a7bda01bd60202b6 Mon Sep 17 00:00:00 2001 From: Bodhi Mulders Date: Fri, 13 Nov 2020 14:10:14 +0100 Subject: [PATCH 19/89] Update packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart 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 243fdfa75879..2357d3ce0b45 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 @@ -80,7 +80,7 @@ abstract class CameraPlatform extends PlatformInterface { /// Starts a video recording and returns the file where it will be saved. /// /// The file is written on the flight as the video is being recorded. - /// The file can be read as soon as [stopVideoRecording] returns it. + /// The file can be read as soon as [stopVideoRecording] returns. Future startVideoRecording(int cameraId) { throw UnimplementedError('startVideoRecording() is not implemented.'); } From 6c32fa94722402e1158b047223090c6bc48a39bd Mon Sep 17 00:00:00 2001 From: Bodhi Mulders Date: Fri, 13 Nov 2020 14:10:57 +0100 Subject: [PATCH 20/89] Update packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart Co-authored-by: Maurits van Beusekom --- .../lib/src/platform_interface/camera_platform.dart | 4 ++++ 1 file changed, 4 insertions(+) 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 2357d3ce0b45..5e4a6b9ffe6e 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 @@ -86,6 +86,10 @@ abstract class CameraPlatform extends PlatformInterface { } /// Stops the video recording. + /// + /// When the [stopVideoRecording] method completes successfully the recorded + /// video can be accessed through the file returned by the + /// [startVideoRecording] method. Future stopVideoRecording(int cameraId) { throw UnimplementedError('stopVideoRecording() is not implemented.'); } From 7a387203cc9d37c3092c5e4e497f29cccdc6ec6d Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 14:16:28 +0100 Subject: [PATCH 21/89] Added placeholders in default method channel implementation --- .../method_channel/method_channel_camera.dart | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) 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 5d5ba4a401d9..910212367be1 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 @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; import 'package:stream_transform/stream_transform.dart'; @@ -23,6 +24,7 @@ class MethodChannelCamera extends CameraPlatform { _cameraEventStreamController.stream .where((event) => event.cameraId == cameraId); + // TODO(BeMacized): Write unit tests. @override Future> availableCameras() async { try { @@ -100,6 +102,48 @@ class MethodChannelCamera extends CameraPlatform { return _events(cameraId).whereType(); } + // TODO(BeMacized): Check signature. Implement. Unit test. + @override + Future takePicture(int cameraId, String path) { + super.takePicture(cameraId, path); + } + + // TODO(BeMacized): Unit Test + @override + Future prepareForVideoRecording() async { + await _channel.invokeMethod('prepareForVideoRecording'); + } + + // TODO(BeMacized): Check signature. Implement. Unit test. + @override + Future startVideoRecording(int cameraId, String path) { + super.startVideoRecording(cameraId, path); + } + + // TODO(BeMacized): Implement. Unit test. + @override + Future stopVideoRecording(int cameraId) { + super.stopVideoRecording(cameraId); + } + + // TODO(BeMacized): Implement. Unit test. + @override + Future pauseVideoRecording(int cameraId) { + super.pauseVideoRecording(cameraId); + } + + // TODO(BeMacized): Implement. Unit test. + @override + Future resumeVideoRecording(int cameraId) { + super.resumeVideoRecording(cameraId); + } + + // TODO(BeMacized): Unit test + @override + Widget buildView(int cameraId) { + return Texture(textureId: cameraId); + } + /// Returns the resolution preset as a String. String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { switch (resolutionPreset) { From 5b90760a961a3e71f05a0699b49f54c98015a785 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 14:27:36 +0100 Subject: [PATCH 22/89] Add missing implementations to default method channel implementation --- .../method_channel/method_channel_camera.dart | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 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 fe511f6128c8..1cbdef4d96f4 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 @@ -103,10 +103,14 @@ class MethodChannelCamera extends CameraPlatform { return _events(cameraId).whereType(); } - // TODO(BeMacized): Implement. Unit test. + // TODO(BeMacized): Unit test. @override - Future takePicture(int cameraId) { - super.takePicture(cameraId); + Future takePicture(int cameraId) async { + String path = await _channel.invokeMethod( + 'takePicture', + {'textureId': cameraId}, + ); + return XFile(path); } // TODO(BeMacized): Unit Test @@ -115,28 +119,41 @@ class MethodChannelCamera extends CameraPlatform { await _channel.invokeMethod('prepareForVideoRecording'); } - // TODO(BeMacized): Implement. Unit test. + // TODO(BeMacized): Unit test. @override - Future startVideoRecording(int cameraId) { - super.startVideoRecording(cameraId); + Future startVideoRecording(int cameraId) async { + String path = await _channel.invokeMethod( + 'startVideoRecording', + {'textureId': cameraId}, + ); + return XFile(path); } - // TODO(BeMacized): Implement. Unit test. + // TODO(BeMacized): Unit test. @override - Future stopVideoRecording(int cameraId) { - super.stopVideoRecording(cameraId); + Future stopVideoRecording(int cameraId) async { + await _channel.invokeMethod( + 'stopVideoRecording', + {'textureId': cameraId}, + ); } - // TODO(BeMacized): Implement. Unit test. + // TODO(BeMacized): Unit test. @override - Future pauseVideoRecording(int cameraId) { - super.pauseVideoRecording(cameraId); + Future pauseVideoRecording(int cameraId) async { + await _channel.invokeMethod( + 'pauseVideoRecording', + {'textureId': cameraId}, + ); } - // TODO(BeMacized): Implement. Unit test. + // TODO(BeMacized): Unit test. @override - Future resumeVideoRecording(int cameraId) { - super.resumeVideoRecording(cameraId); + Future resumeVideoRecording(int cameraId) async { + await _channel.invokeMethod( + 'resumeVideoRecording', + {'textureId': cameraId}, + ); } // TODO(BeMacized): Unit test From 6eb0e38df00213902f282be317d9709f5c642334 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 13 Nov 2020 14:36:59 +0100 Subject: [PATCH 23/89] Fix formatting --- .../lib/src/platform_interface/camera_platform.dart | 2 +- .../test/camera_platform_interface_test.dart | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) 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 5e4a6b9ffe6e..3437ac256678 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 { /// Stops the video recording. /// /// When the [stopVideoRecording] method completes successfully the recorded - /// video can be accessed through the file returned by the + /// video can be accessed through the file returned by the /// [startVideoRecording] method. Future stopVideoRecording(int cameraId) { throw UnimplementedError('stopVideoRecording() is not implemented.'); diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index bccdf3a86abd..ee60b2f1f069 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -57,7 +57,6 @@ void main() { ); }); - test( 'Default implementation of onCameraClosing() should throw unimplemented error', () { From af219853218a0a529e05129c0d7a982974649e5d Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 14:39:26 +0100 Subject: [PATCH 24/89] Fix PR feedback --- .../lib/src/events/camera_event.dart | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 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 d42523941c3f..c74b94e590b0 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 @@ -21,6 +21,16 @@ abstract class CameraEvent { /// /// The `cameraId` is the ID of the camera that triggered the event. CameraEvent(this.cameraId); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is CameraEvent && + runtimeType == other.runtimeType && + cameraId == other.cameraId; + + @override + int get hashCode => cameraId.hashCode; } /// An event fired when the resolution preset of the camera has changed. @@ -69,6 +79,7 @@ class ResolutionChangedEvent extends CameraEvent { bool operator ==(Object other) => identical(this, other) || other is ResolutionChangedEvent && + super == (other) && runtimeType == other.runtimeType && captureWidth == other.captureWidth && captureHeight == other.captureHeight && @@ -77,6 +88,7 @@ class ResolutionChangedEvent extends CameraEvent { @override int get hashCode => + super.hashCode ^ captureWidth.hashCode ^ captureHeight.hashCode ^ previewWidth.hashCode ^ @@ -99,10 +111,12 @@ class CameraClosingEvent extends CameraEvent { @override bool operator ==(Object other) => identical(this, other) || - other is CameraClosingEvent && runtimeType == other.runtimeType; + super == (other) && + other is CameraClosingEvent && + runtimeType == other.runtimeType; @override - int get hashCode => 0; + int get hashCode => super.hashCode; } /// An event fired when an error occured while operating the camera. @@ -128,10 +142,11 @@ class CameraErrorEvent extends CameraEvent { @override bool operator ==(Object other) => identical(this, other) || - other is CameraErrorEvent && + super == (other) && + other is CameraErrorEvent && runtimeType == other.runtimeType && description == other.description; @override - int get hashCode => description.hashCode; + int get hashCode => super.hashCode ^ description.hashCode; } From 7ba6b1d4df92c5db2d2cd10d15aaa668be3da938 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:14:10 +0100 Subject: [PATCH 25/89] Add unit test for availableCameras --- .../method_channel/method_channel_camera.dart | 6 +-- .../test/method_channel_camera_test.dart | 51 +++++++++++++++++-- 2 files changed, 51 insertions(+), 6 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 1cbdef4d96f4..20462d7f4c19 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 @@ -25,7 +25,6 @@ class MethodChannelCamera extends CameraPlatform { _cameraEventStreamController.stream .where((event) => event.cameraId == cameraId); - // TODO(BeMacized): Write unit tests. @override Future> availableCameras() async { try { @@ -34,7 +33,7 @@ class MethodChannelCamera extends CameraPlatform { return cameras.map((Map camera) { return CameraDescription( name: camera['name'], - lensDirection: _parseCameraLensDirection(camera['lensFacing']), + lensDirection: parseCameraLensDirection(camera['lensFacing']), sensorOrientation: camera['sensorOrientation'], ); }).toList(); @@ -182,7 +181,8 @@ class MethodChannelCamera extends CameraPlatform { } // Parses a string into a corresponding CameraLensDirection. - CameraLensDirection _parseCameraLensDirection(String string) { + @visibleForTesting + CameraLensDirection parseCameraLensDirection(String string) { switch (string) { case 'front': return CameraLensDirection.front; diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 49f037c36c7b..621b21120ff3 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -77,12 +77,11 @@ void main() { }); }); - group('Interaction Tests', () { - MethodChannelMock channel; + group('Event Tests', () { MethodChannelCamera camera; int cameraId; setUp(() async { - channel = MethodChannelMock( + MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'initialize': {'textureId': 1}, @@ -176,5 +175,51 @@ void main() { await streamQueue.cancel(); }); }); + + group('Function Tests', () { + MethodChannelCamera camera; + int cameraId; + setUp(() async { + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'initialize': {'textureId': 1}, + }, + ); + camera = MethodChannelCamera(); + cameraId = await camera.initializeCamera( + CameraDescription(name: 'Test'), + ResolutionPreset.high, + ); + }); + + test('Should fetch CameraDescription instances for available cameras', + () async { + // Arrange + List> returnData = [ + {'name': 'Test 1', 'lensFacing': 'front', 'sensorOrientation': 1}, + {'name': 'Test 2', 'lensFacing': 'back', 'sensorOrientation': 2} + ]; + MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'availableCameras': returnData}, + ); + + // Act + List cameras = await camera.availableCameras(); + + // Assert + expect(cameras.length, returnData.length); + for (int i = 0; i < returnData.length; i++) { + CameraDescription cameraDescription = CameraDescription( + name: returnData[i]['name'], + lensDirection: + camera.parseCameraLensDirection(returnData[i]['lensFacing']), + sensorOrientation: returnData[i]['sensorOrientation'], + ); + expect(cameras[i], cameraDescription); + } + }); + }); }); } From a9cd0aa5ba5fbbaeeb209a612a410be7ae368375 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:25:30 +0100 Subject: [PATCH 26/89] Expand availableCameras unit test. Added unit test for takePicture. --- .../method_channel/method_channel_camera.dart | 1 - .../test/method_channel_camera_test.dart | 22 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 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 20462d7f4c19..4b894c03b5c3 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 @@ -102,7 +102,6 @@ class MethodChannelCamera extends CameraPlatform { return _events(cameraId).whereType(); } - // TODO(BeMacized): Unit test. @override Future takePicture(int cameraId) async { String path = await _channel.invokeMethod( diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 621b21120ff3..9353a5fa6ae1 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; +import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -200,7 +201,7 @@ void main() { {'name': 'Test 1', 'lensFacing': 'front', 'sensorOrientation': 1}, {'name': 'Test 2', 'lensFacing': 'back', 'sensorOrientation': 2} ]; - MethodChannelMock( + MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'availableCameras': returnData}, ); @@ -209,6 +210,9 @@ void main() { List cameras = await camera.availableCameras(); // Assert + expect(channel.log, [ + isMethodCall('availableCameras', arguments: null), + ]); expect(cameras.length, returnData.length); for (int i = 0; i < returnData.length; i++) { CameraDescription cameraDescription = CameraDescription( @@ -220,6 +224,22 @@ void main() { expect(cameras[i], cameraDescription); } }); + + test('Should take a picture and return an XFile instance', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'takePicture': '/test/path.jpg'}); + + // Act + XFile file = await camera.takePicture(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('takePicture', arguments: {'textureId': cameraId}), + ]); + expect(file.path, '/test/path.jpg'); + }); }); }); } From e56dd6c01e3adab95f06f9124ac524413de26da3 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:47:39 +0100 Subject: [PATCH 27/89] Add unit test for startVideoRecording --- .../method_channel/method_channel_camera.dart | 1 - .../test/method_channel_camera_test.dart | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) 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 4b894c03b5c3..35bb26ae55b4 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 @@ -117,7 +117,6 @@ class MethodChannelCamera extends CameraPlatform { await _channel.invokeMethod('prepareForVideoRecording'); } - // TODO(BeMacized): Unit test. @override Future startVideoRecording(int cameraId) async { String path = await _channel.invokeMethod( diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 9353a5fa6ae1..e5752753af32 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -240,6 +240,24 @@ void main() { ]); expect(file.path, '/test/path.jpg'); }); + + test('Should start recording a video and return an XFile instance', + () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startVideoRecording': '/test/path.mkv'}); + + // Act + XFile file = await camera.startVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('startVideoRecording', + arguments: {'textureId': cameraId}), + ]); + expect(file.path, '/test/path.mkv'); + }); }); }); } From ea263a75f3e7971a173e9a6eff1232e0a6cb7f16 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:49:47 +0100 Subject: [PATCH 28/89] Add unit test for prepareForVideoRecording --- .../method_channel/method_channel_camera.dart | 1 - .../test/method_channel_camera_test.dart | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) 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 35bb26ae55b4..01fb25428cf5 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 @@ -111,7 +111,6 @@ class MethodChannelCamera extends CameraPlatform { return XFile(path); } - // TODO(BeMacized): Unit Test @override Future prepareForVideoRecording() async { await _channel.invokeMethod('prepareForVideoRecording'); diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index e5752753af32..924a639f2f16 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -241,6 +241,22 @@ void main() { expect(file.path, '/test/path.jpg'); }); + test('Should prepare for video recording', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'prepareForVideoRecording': null}, + ); + + // Act + await camera.prepareForVideoRecording(); + + // Assert + expect(channel.log, [ + isMethodCall('prepareForVideoRecording', arguments: null), + ]); + }); + test('Should start recording a video and return an XFile instance', () async { // Arrange From ae8cfc0bfa4e751974ce2b63c51f29bce62c4d73 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:51:33 +0100 Subject: [PATCH 29/89] Add unit test for stopVideoRecording --- .../method_channel/method_channel_camera.dart | 1 - .../test/method_channel_camera_test.dart | 27 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 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 01fb25428cf5..6de1bf6afb6f 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 @@ -125,7 +125,6 @@ class MethodChannelCamera extends CameraPlatform { return XFile(path); } - // TODO(BeMacized): Unit test. @override Future stopVideoRecording(int cameraId) async { await _channel.invokeMethod( diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 924a639f2f16..360c953e7808 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -236,7 +236,9 @@ void main() { // Assert expect(channel.log, [ - isMethodCall('takePicture', arguments: {'textureId': cameraId}), + isMethodCall('takePicture', arguments: { + 'textureId': cameraId, + }), ]); expect(file.path, '/test/path.jpg'); }); @@ -269,11 +271,30 @@ void main() { // Assert expect(channel.log, [ - isMethodCall('startVideoRecording', - arguments: {'textureId': cameraId}), + isMethodCall('startVideoRecording', arguments: { + 'textureId': cameraId, + }), ]); expect(file.path, '/test/path.mkv'); }); + + test('Should stop a video recording', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'stopVideoRecording': null}, + ); + + // Act + await camera.stopVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('stopVideoRecording', arguments: { + 'textureId': cameraId, + }), + ]); + }); }); }); } From 782fe6afe20c2898c5df5b689f3e2e4455ff747f Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:52:22 +0100 Subject: [PATCH 30/89] Add unit test for pauseVideoRecording --- .../method_channel/method_channel_camera.dart | 1 - .../test/method_channel_camera_test.dart | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) 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 6de1bf6afb6f..516cb16992a1 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 @@ -133,7 +133,6 @@ class MethodChannelCamera extends CameraPlatform { ); } - // TODO(BeMacized): Unit test. @override Future pauseVideoRecording(int cameraId) async { await _channel.invokeMethod( diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 360c953e7808..5f6ec85ebf4f 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -295,6 +295,24 @@ void main() { }), ]); }); + + test('Should pause a video recording', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'pauseVideoRecording': null}, + ); + + // Act + await camera.pauseVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('pauseVideoRecording', arguments: { + 'textureId': cameraId, + }), + ]); + }); }); }); } From d701a7622cd1c01b80108773d85db97ed26d39af Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:56:00 +0100 Subject: [PATCH 31/89] Add unit test for buildView --- .../method_channel/method_channel_camera.dart | 2 -- .../test/method_channel_camera_test.dart | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 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 516cb16992a1..b783fd9f06a8 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 @@ -141,7 +141,6 @@ class MethodChannelCamera extends CameraPlatform { ); } - // TODO(BeMacized): Unit test. @override Future resumeVideoRecording(int cameraId) async { await _channel.invokeMethod( @@ -150,7 +149,6 @@ class MethodChannelCamera extends CameraPlatform { ); } - // TODO(BeMacized): Unit test @override Widget buildView(int cameraId) { return Texture(textureId: cameraId); diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 5f6ec85ebf4f..e715e802b5dc 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -9,6 +9,7 @@ import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'utils/method_channel_mock.dart'; @@ -313,6 +314,33 @@ void main() { }), ]); }); + + test('Should resume a video recording', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'resumeVideoRecording': null}, + ); + + // Act + await camera.resumeVideoRecording(cameraId); + + // Assert + expect(channel.log, [ + isMethodCall('resumeVideoRecording', arguments: { + 'textureId': cameraId, + }), + ]); + }); + + test('Should build a texture widget as view widget', () async { + // Act + Widget widget = camera.buildView(cameraId); + + // Act + expect(widget is Texture, isTrue); + expect((widget as Texture).textureId, cameraId); + }); }); }); } From debb8bc92ec0616b91401069e5f0fb1545f725cb Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 13 Nov 2020 15:56:26 +0100 Subject: [PATCH 32/89] Remove TODO comment --- .../lib/src/method_channel/method_channel_camera.dart | 2 -- 1 file changed, 2 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 b783fd9f06a8..b68c243c1e8e 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 @@ -15,8 +15,6 @@ const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); /// An implementation of [CameraPlatform] that uses method channels. class MethodChannelCamera extends CameraPlatform { - // TODO(mvanbeusekom): Add default method channel implementation of the - // [CameraPlatform] interface. final Map _channels = {}; final StreamController _cameraEventStreamController = StreamController.broadcast(); From 55782fda3cac865994d69b23f8ff4ef9f13480bd Mon Sep 17 00:00:00 2001 From: Bodhi Mulders Date: Fri, 13 Nov 2020 16:19:44 +0100 Subject: [PATCH 33/89] Update packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart Co-authored-by: Maurits van Beusekom --- .../lib/src/method_channel/method_channel_camera.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 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 b68c243c1e8e..cefa12d97503 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 @@ -110,9 +110,8 @@ class MethodChannelCamera extends CameraPlatform { } @override - Future prepareForVideoRecording() async { - await _channel.invokeMethod('prepareForVideoRecording'); - } + Future prepareForVideoRecording() => + _channel.invokeMethod('prepareForVideoRecording'); @override Future startVideoRecording(int cameraId) async { From 576ef94444aeb10b63532db3b3f12c84529d7a52 Mon Sep 17 00:00:00 2001 From: Bodhi Mulders Date: Fri, 13 Nov 2020 16:20:06 +0100 Subject: [PATCH 34/89] Update packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart Co-authored-by: Maurits van Beusekom --- .../lib/src/method_channel/method_channel_camera.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 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 cefa12d97503..cf8d71af033a 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 @@ -131,12 +131,11 @@ class MethodChannelCamera extends CameraPlatform { } @override - Future pauseVideoRecording(int cameraId) async { - await _channel.invokeMethod( + Future pauseVideoRecording(int cameraId) => + _channel.invokeMethod( 'pauseVideoRecording', {'textureId': cameraId}, ); - } @override Future resumeVideoRecording(int cameraId) async { From 8e7e954141edece7f23080693d50f7b40fe6cca3 Mon Sep 17 00:00:00 2001 From: Bodhi Mulders Date: Fri, 13 Nov 2020 16:20:20 +0100 Subject: [PATCH 35/89] Update packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart Co-authored-by: Maurits van Beusekom --- .../lib/src/method_channel/method_channel_camera.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 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 cf8d71af033a..6530584ecc08 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 @@ -138,12 +138,11 @@ class MethodChannelCamera extends CameraPlatform { ); @override - Future resumeVideoRecording(int cameraId) async { - await _channel.invokeMethod( + Future resumeVideoRecording(int cameraId) => + _channel.invokeMethod( 'resumeVideoRecording', {'textureId': cameraId}, ); - } @override Widget buildView(int cameraId) { From 821710c0fae5a88d7439f73fbae88b3b7e8854bf Mon Sep 17 00:00:00 2001 From: daniel Date: Thu, 19 Nov 2020 09:59:01 +0100 Subject: [PATCH 36/89] WIP: Dart and Android implementation --- .../flutter/plugins/camera/DartMessenger.java | 4 +- .../example/integration_test/camera_test.dart | 1 + packages/camera/camera/example/lib/main.dart | 64 +++--- packages/camera/camera/lib/camera.dart | 205 +++--------------- packages/camera/camera/pubspec.yaml | 2 + .../lib/camera_platform_interface.dart | 2 + .../method_channel/method_channel_camera.dart | 2 +- 7 files changed, 64 insertions(+), 216 deletions(-) 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 fe385bef7818..08b36cb2cb87 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 @@ -16,7 +16,7 @@ enum EventType { } DartMessenger(BinaryMessenger messenger, long eventChannelId) { - new EventChannel(messenger, "flutter.io/cameraPlugin/cameraEvents" + eventChannelId) + new EventChannel(messenger, "flutter.io/cameraPlugin/camera" + eventChannelId) .setStreamHandler( new EventChannel.StreamHandler() { @Override @@ -44,7 +44,7 @@ void send(EventType eventType, @Nullable String description) { event.put("eventType", eventType.toString().toLowerCase()); // Only errors have a description. if (eventType == EventType.ERROR && !TextUtils.isEmpty(description)) { - event.put("errorDescription", description); + event.put("description", description); } eventSink.success(event); } diff --git a/packages/camera/camera/example/integration_test/camera_test.dart b/packages/camera/camera/example/integration_test/camera_test.dart index ef4646f5ced9..146126963632 100644 --- a/packages/camera/camera/example/integration_test/camera_test.dart +++ b/packages/camera/camera/example/integration_test/camera_test.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:ui'; +import 'package:camera/camera.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:camera/camera.dart'; diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 3ec6604ad788..a23135f64f71 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -38,8 +38,8 @@ void logError(String code, String message) => class _CameraExampleHomeState extends State with WidgetsBindingObserver { CameraController controller; - String imagePath; - String videoPath; + XFile imageFile; + XFile videoFile; VideoPlayerController videoController; VoidCallback videoPlayerListener; bool enableAudio = true; @@ -130,9 +130,9 @@ class _CameraExampleHomeState extends State ), ); } else { - return AspectRatio( - aspectRatio: controller.value.aspectRatio, - child: CameraPreview(controller), + return Container( + // aspectRatio: controller.value.aspectRatio, + child: controller.buildView(), ); } } @@ -166,11 +166,11 @@ class _CameraExampleHomeState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - videoController == null && imagePath == null + videoController == null && imageFile == null ? Container() : SizedBox( child: (videoController == null) - ? Image.file(File(imagePath)) + ? Image.file(File(imageFile.path)) : Container( child: Center( child: AspectRatio( @@ -306,29 +306,29 @@ class _CameraExampleHomeState extends State } void onTakePictureButtonPressed() { - takePicture().then((String filePath) { + takePicture().then((XFile file) { if (mounted) { setState(() { - imagePath = filePath; + imageFile = file; videoController?.dispose(); videoController = null; }); - if (filePath != null) showInSnackBar('Picture saved to $filePath'); + if (file != null) showInSnackBar('Picture saved to ${file.path}'); } }); } void onVideoRecordButtonPressed() { - startVideoRecording().then((String filePath) { + startVideoRecording().then((XFile file) { if (mounted) setState(() {}); - if (filePath != null) showInSnackBar('Saving video to $filePath'); + if (file != null) showInSnackBar('Saving video to ${file.path}'); }); } void onStopButtonPressed() { stopVideoRecording().then((_) { if (mounted) setState(() {}); - showInSnackBar('Video recorded to: $videoPath'); + showInSnackBar('Video recorded to: ${videoFile.path}'); }); } @@ -346,30 +346,24 @@ class _CameraExampleHomeState extends State }); } - Future startVideoRecording() async { + Future startVideoRecording() async { if (!controller.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return null; } - final Directory extDir = await getApplicationDocumentsDirectory(); - final String dirPath = '${extDir.path}/Movies/flutter_test'; - await Directory(dirPath).create(recursive: true); - final String filePath = '$dirPath/${timestamp()}.mp4'; - if (controller.value.isRecordingVideo) { // A recording is already started, do nothing. return null; } try { - videoPath = filePath; - await controller.startVideoRecording(filePath); + XFile file = await controller.startVideoRecording(); + return file; } on CameraException catch (e) { _showCameraException(e); return null; } - return filePath; } Future stopVideoRecording() async { @@ -414,8 +408,8 @@ class _CameraExampleHomeState extends State } Future _startVideoPlayer() async { - final VideoPlayerController vcontroller = - VideoPlayerController.file(File(videoPath)); + final VideoPlayerController vController = + VideoPlayerController.file(File(videoFile.path)); videoPlayerListener = () { if (videoController != null && videoController.value.size != null) { // Refreshing the state to update video player with the correct ratio. @@ -423,28 +417,24 @@ class _CameraExampleHomeState extends State videoController.removeListener(videoPlayerListener); } }; - vcontroller.addListener(videoPlayerListener); - await vcontroller.setLooping(true); - await vcontroller.initialize(); + vController.addListener(videoPlayerListener); + await vController.setLooping(true); + await vController.initialize(); await videoController?.dispose(); if (mounted) { setState(() { - imagePath = null; - videoController = vcontroller; + imageFile = null; + videoController = vController; }); } - await vcontroller.play(); + await vController.play(); } - Future takePicture() async { + Future takePicture() async { if (!controller.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return null; } - final Directory extDir = await getApplicationDocumentsDirectory(); - final String dirPath = '${extDir.path}/Pictures/flutter_test'; - await Directory(dirPath).create(recursive: true); - final String filePath = '$dirPath/${timestamp()}.jpg'; if (controller.value.isTakingPicture) { // A capture is already pending, do nothing. @@ -452,12 +442,12 @@ class _CameraExampleHomeState extends State } try { - await controller.takePicture(filePath); + XFile file = await controller.takePicture(); + return file; } on CameraException catch (e) { _showCameraException(e); return null; } - return filePath; } void _showCameraException(CameraException e) { diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 3b2cd77c5757..551efd65139a 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -5,141 +5,28 @@ import 'dart:async'; import 'dart:typed_data'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +export 'package:camera_platform_interface/camera_platform_interface.dart'; + part 'camera_image.dart'; final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); -/// The direction the camera is facing. -enum CameraLensDirection { - /// Front facing camera (a user looking at the screen is seen by the camera). - front, - - /// Back facing camera (a user looking at the screen is not seen by the camera). - back, - - /// External camera which may not be mounted to the device. - external, -} - -/// Affect the quality of video recording and image capture: -/// -/// If a preset is not available on the camera being used a preset of lower quality will be selected automatically. -enum ResolutionPreset { - /// 352x288 on iOS, 240p (320x240) on Android - low, - - /// 480p (640x480 on iOS, 720x480 on Android) - medium, - - /// 720p (1280x720) - high, - - /// 1080p (1920x1080) - veryHigh, - - /// 2160p (3840x2160) - ultraHigh, - - /// The highest resolution available. - max, -} - /// Signature for a callback receiving the a camera image. /// /// This is used by [CameraController.startImageStream]. // ignore: inference_failure_on_function_return_type typedef onLatestImageAvailable = Function(CameraImage image); -/// Returns the resolution preset as a String. -String serializeResolutionPreset(ResolutionPreset resolutionPreset) { - switch (resolutionPreset) { - case ResolutionPreset.max: - return 'max'; - case ResolutionPreset.ultraHigh: - return 'ultraHigh'; - case ResolutionPreset.veryHigh: - return 'veryHigh'; - case ResolutionPreset.high: - return 'high'; - case ResolutionPreset.medium: - return 'medium'; - case ResolutionPreset.low: - return 'low'; - } - throw ArgumentError('Unknown ResolutionPreset value'); -} - -CameraLensDirection _parseCameraLensDirection(String string) { - switch (string) { - case 'front': - return CameraLensDirection.front; - case 'back': - return CameraLensDirection.back; - case 'external': - return CameraLensDirection.external; - } - throw ArgumentError('Unknown CameraLensDirection value'); -} - /// Completes with a list of available cameras. /// /// May throw a [CameraException]. Future> availableCameras() async { - try { - final List> cameras = await _channel - .invokeListMethod>('availableCameras'); - return cameras.map((Map camera) { - return CameraDescription( - name: camera['name'], - lensDirection: _parseCameraLensDirection(camera['lensFacing']), - sensorOrientation: camera['sensorOrientation'], - ); - }).toList(); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } -} - -/// Properties of a camera device. -class CameraDescription { - /// Creates a new camera description with the given properties. - CameraDescription({this.name, this.lensDirection, this.sensorOrientation}); - - /// The name of the camera device. - final String name; - - /// The direction the camera is facing. - final CameraLensDirection lensDirection; - - /// Clockwise angle through which the output image needs to be rotated to be upright on the device screen in its native orientation. - /// - /// **Range of valid values:** - /// 0, 90, 180, 270 - /// - /// On Android, also defines the direction of rolling shutter readout, which - /// is from top to bottom in the sensor's coordinate system. - final int sensorOrientation; - - @override - bool operator ==(Object o) { - return o is CameraDescription && - o.name == name && - o.lensDirection == lensDirection; - } - - @override - int get hashCode { - return hashValues(name, lensDirection); - } - - @override - String toString() { - return '$runtimeType($name, $lensDirection, $sensorOrientation)'; - } + return CameraPlatform.instance.availableCameras(); } /// This is thrown when the plugin reports an error. @@ -170,7 +57,7 @@ class CameraPreview extends StatelessWidget { @override Widget build(BuildContext context) { return controller.value.isInitialized - ? Texture(textureId: controller._textureId) + ? Texture(textureId: controller._cameraId) : Container(); } } @@ -188,7 +75,7 @@ class CameraValue { bool isRecordingPaused, }) : _isRecordingPaused = isRecordingPaused; - /// Creates a new camera controller state for an uninitialzed controller. + /// Creates a new camera controller state for an uninitialized controller. const CameraValue.uninitialized() : this( isInitialized: false, @@ -301,7 +188,7 @@ class CameraController extends ValueNotifier { /// Whether to include audio when recording a video. final bool enableAudio; - int _textureId; + int _cameraId; bool _isDisposed = false; StreamSubscription _eventSubscription; StreamSubscription _imageStreamSubscription; @@ -323,28 +210,17 @@ class CameraController extends ValueNotifier { } try { _creatingCompleter = Completer(); - final Map reply = - await _channel.invokeMapMethod( - 'initialize', - { - 'cameraName': description.name, - 'resolutionPreset': serializeResolutionPreset(resolutionPreset), - 'enableAudio': enableAudio, - }, - ); - _textureId = reply['textureId']; - value = value.copyWith( - isInitialized: true, - previewSize: Size( - reply['previewWidth'].toDouble(), - reply['previewHeight'].toDouble(), - ), - ); + + _cameraId = await CameraPlatform.instance.initializeCamera( + description, resolutionPreset, + enableAudio: enableAudio); + + value = value.copyWith(isInitialized: true); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } _eventSubscription = - EventChannel('flutter.io/cameraPlugin/cameraEvents$_textureId') + EventChannel('flutter.io/cameraPlugin/camera$_cameraId') .receiveBroadcastStream() .listen(_listener); _creatingCompleter.complete(); @@ -363,13 +239,15 @@ class CameraController extends ValueNotifier { /// /// Throws a [CameraException] if the prepare fails. Future prepareForVideoRecording() async { - await _channel.invokeMethod('prepareForVideoRecording'); + await CameraPlatform.instance.prepareForVideoRecording(); } /// Listen to events from the native plugins. /// /// A "cameraClosing" event is sent when the camera is closed automatically by the system (for example when the app go to background). The plugin will try to reopen the camera automatically but any ongoing recording will end. void _listener(dynamic event) { + //TODO: Replace + debugPrint("event is $event"); final Map map = event; if (_isDisposed) { return; @@ -394,7 +272,7 @@ class CameraController extends ValueNotifier { /// The file can be read as this function returns. /// /// Throws a [CameraException] if the capture fails. - Future takePicture(String path) async { + Future takePicture() async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController.', @@ -409,11 +287,9 @@ class CameraController extends ValueNotifier { } try { value = value.copyWith(isTakingPicture: true); - await _channel.invokeMethod( - 'takePicture', - {'textureId': _textureId, 'path': path}, - ); + XFile file = await CameraPlatform.instance.takePicture(_cameraId); value = value.copyWith(isTakingPicture: false); + return file; } on PlatformException catch (e) { value = value.copyWith(isTakingPicture: false); throw CameraException(e.code, e.message); @@ -433,7 +309,7 @@ class CameraController extends ValueNotifier { /// Throws a [CameraException] if image streaming or video recording has /// already started. // TODO(bmparr): Add settings for resolution and fps. - Future startImageStream(onLatestImageAvailable onAvailable) async { + Widget buildView() { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -454,19 +330,10 @@ class CameraController extends ValueNotifier { } try { - await _channel.invokeMethod('startImageStream'); - value = value.copyWith(isStreamingImages: true); + return CameraPlatform.instance.buildView(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } - const EventChannel cameraEventChannel = - EventChannel('plugins.flutter.io/camera/imageStream'); - _imageStreamSubscription = - cameraEventChannel.receiveBroadcastStream().listen( - (dynamic imageData) { - onAvailable(CameraImage._fromPlatformData(imageData)); - }, - ); } /// Stop streaming images from platform camera. @@ -514,7 +381,7 @@ class CameraController extends ValueNotifier { /// The file can be read as soon as [stopVideoRecording] returns. /// /// Throws a [CameraException] if the capture fails. - Future startVideoRecording(String filePath) async { + Future startVideoRecording() async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -535,11 +402,9 @@ class CameraController extends ValueNotifier { } try { - await _channel.invokeMethod( - 'startVideoRecording', - {'textureId': _textureId, 'filePath': filePath}, - ); + XFile file = await CameraPlatform.instance.startVideoRecording(_cameraId); value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); + return file; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -560,11 +425,8 @@ class CameraController extends ValueNotifier { ); } try { + await CameraPlatform.instance.stopVideoRecording(_cameraId); value = value.copyWith(isRecordingVideo: false); - await _channel.invokeMethod( - 'stopVideoRecording', - {'textureId': _textureId}, - ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -587,11 +449,8 @@ class CameraController extends ValueNotifier { ); } try { + await CameraPlatform.instance.pauseVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: true); - await _channel.invokeMethod( - 'pauseVideoRecording', - {'textureId': _textureId}, - ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -614,11 +473,8 @@ class CameraController extends ValueNotifier { ); } try { + await CameraPlatform.instance.resumeVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: false); - await _channel.invokeMethod( - 'resumeVideoRecording', - {'textureId': _textureId}, - ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -634,10 +490,7 @@ class CameraController extends ValueNotifier { super.dispose(); if (_creatingCompleter != null) { await _creatingCompleter.future; - await _channel.invokeMethod( - 'dispose', - {'textureId': _textureId}, - ); + await CameraPlatform.instance.dispose(_cameraId); await _eventSubscription?.cancel(); } } diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 7088faf227aa..a1c9f3c5843f 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -8,6 +8,8 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: flutter: sdk: flutter + camera_platform_interface: + path: ../camera_platform_interface dev_dependencies: path_provider: ^0.5.0 diff --git a/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart b/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart index 7fec503c9423..4956105d305d 100644 --- a/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart +++ b/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart @@ -5,3 +5,5 @@ export 'src/events/camera_event.dart'; export 'src/platform_interface/camera_platform.dart'; export 'src/types/types.dart'; +/// Expose XFile +export 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; \ 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 6530584ecc08..3d91d8898999 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 @@ -168,7 +168,7 @@ class MethodChannelCamera extends CameraPlatform { throw ArgumentError('Unknown ResolutionPreset value'); } - // Parses a string into a corresponding CameraLensDirection. + /// Parses a string into a corresponding CameraLensDirection. @visibleForTesting CameraLensDirection parseCameraLensDirection(String string) { switch (string) { From 6902112a6424679fa958a81ee58a75fb3246e945 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 19 Nov 2020 11:10:28 +0100 Subject: [PATCH 37/89] Have resolution stream replay last value on subscription. Replace stream_transform with rxdart. --- .../src/method_channel/method_channel_camera.dart | 13 ++++++++----- .../camera/camera_platform_interface/pubspec.yaml | 2 +- .../test/method_channel_camera_test.dart | 3 +-- 3 files changed, 10 insertions(+), 8 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 6530584ecc08..616359e78682 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 @@ -9,16 +9,19 @@ import 'package:file_selector_platform_interface/file_selector_platform_interfac import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; -import 'package:stream_transform/stream_transform.dart'; +import 'package:rxdart/rxdart.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); /// An implementation of [CameraPlatform] that uses method channels. class MethodChannelCamera extends CameraPlatform { final Map _channels = {}; + final StreamController _cameraEventStreamController = StreamController.broadcast(); + final Map _cameraResolutionChangedEventStreams = {}; + Stream _events(int cameraId) => _cameraEventStreamController.stream .where((event) => event.cameraId == cameraId); @@ -69,6 +72,9 @@ class MethodChannelCamera extends CameraPlatform { channel.setMethodCallHandler( (MethodCall call) => handleMethodCall(call, _textureId)); _channels[_textureId] = channel; + _cameraResolutionChangedEventStreams[_textureId] = _events(_textureId) + .whereType() + .shareReplay(maxSize: 1); } return _textureId; } @@ -84,19 +90,16 @@ class MethodChannelCamera extends CameraPlatform { @override Stream onResolutionChanged(int cameraId) { - assert(_channels.containsKey(cameraId)); - return _events(cameraId).whereType(); + return _cameraResolutionChangedEventStreams[cameraId]; } @override Stream onCameraClosing(int cameraId) { - assert(_channels.containsKey(cameraId)); return _events(cameraId).whereType(); } @override Stream onCameraError(int cameraId) { - assert(_channels.containsKey(cameraId)); return _events(cameraId).whereType(); } diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 4c393f8c4ccf..9b2065e792e8 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: meta: ^1.0.5 plugin_platform_interface: ^1.0.1 file_selector_platform_interface: ^1.0.0 - stream_transform: ^1.2.0 + rxdart: ^0.24.1 dev_dependencies: flutter_test: diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index e715e802b5dc..f0ac45c239ec 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -33,7 +33,6 @@ void main() { CameraDescription(name: 'Test'), ResolutionPreset.high, ); - print('cameraId $cameraId'); // Assert expect(cameraId, 1); @@ -334,7 +333,7 @@ void main() { }); test('Should build a texture widget as view widget', () async { - // Act + // Act Widget widget = camera.buildView(cameraId); // Act From 4c217eda6d6a9ceef6580a057463ee586bc888bf Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 19 Nov 2020 13:41:56 +0100 Subject: [PATCH 38/89] Added reverse method channel to replace event channel. Updated initialise and takePicture implementations for android. WIP implementation for startVideoRecording --- .../io/flutter/plugins/camera/Camera.java | 870 +++++++++--------- .../flutter/plugins/camera/DartMessenger.java | 84 +- .../plugins/camera/MethodCallHandlerImpl.java | 5 +- packages/camera/camera/lib/camera.dart | 20 +- .../method_channel/method_channel_camera.dart | 34 +- .../test/method_channel_camera_test.dart | 22 +- 6 files changed, 526 insertions(+), 509 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 2384393c7d2b..480ee7c35ab4 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 @@ -30,6 +30,7 @@ import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.media.MediaRecorderBuilder; import io.flutter.view.TextureRegistry.SurfaceTextureEntry; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -41,471 +42,476 @@ import java.util.Map; public class Camera { - private final SurfaceTextureEntry flutterTexture; - private final CameraManager cameraManager; - private final OrientationEventListener orientationEventListener; - private final boolean isFrontFacing; - private final int sensorOrientation; - private final String cameraName; - private final Size captureSize; - private final Size previewSize; - private final boolean enableAudio; - - private CameraDevice cameraDevice; - private CameraCaptureSession cameraCaptureSession; - private ImageReader pictureImageReader; - private ImageReader imageStreamReader; - private DartMessenger dartMessenger; - private CaptureRequest.Builder captureRequestBuilder; - private MediaRecorder mediaRecorder; - private boolean recordingVideo; - private CamcorderProfile recordingProfile; - private int currentOrientation = ORIENTATION_UNKNOWN; - - // Mirrors camera.dart - public enum ResolutionPreset { - low, - medium, - high, - veryHigh, - ultraHigh, - max, - } - - public Camera( - final Activity activity, - final SurfaceTextureEntry flutterTexture, - final DartMessenger dartMessenger, - final String cameraName, - final String resolutionPreset, - final boolean enableAudio) - throws CameraAccessException { - if (activity == null) { - throw new IllegalStateException("No activity available!"); - } - this.cameraName = cameraName; - this.enableAudio = enableAudio; - this.flutterTexture = flutterTexture; - this.dartMessenger = dartMessenger; - this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); - orientationEventListener = - new OrientationEventListener(activity.getApplicationContext()) { - @Override - public void onOrientationChanged(int i) { - if (i == ORIENTATION_UNKNOWN) { - return; - } - // Convert the raw deg angle to the nearest multiple of 90. - currentOrientation = (int) Math.round(i / 90.0) * 90; - } - }; - orientationEventListener.enable(); - - CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); - StreamConfigurationMap streamConfigurationMap = - characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - //noinspection ConstantConditions - sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); - //noinspection ConstantConditions - isFrontFacing = - characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT; - ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset); - recordingProfile = - CameraUtils.getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset); - captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); - previewSize = computeBestPreviewSize(cameraName, preset); - } - - private void prepareMediaRecorder(String outputFilePath) throws IOException { - if (mediaRecorder != null) { - mediaRecorder.release(); + private final SurfaceTextureEntry flutterTexture; + private final CameraManager cameraManager; + private final OrientationEventListener orientationEventListener; + private final boolean isFrontFacing; + private final int sensorOrientation; + private final String cameraName; + private final Size captureSize; + private final Size previewSize; + private final boolean enableAudio; + + private CameraDevice cameraDevice; + private CameraCaptureSession cameraCaptureSession; + private ImageReader pictureImageReader; + private ImageReader imageStreamReader; + private DartMessenger dartMessenger; + private CaptureRequest.Builder captureRequestBuilder; + private MediaRecorder mediaRecorder; + private boolean recordingVideo; + private CamcorderProfile recordingProfile; + private int currentOrientation = ORIENTATION_UNKNOWN; + private Context applicationContext; + + // Mirrors camera.dart + public enum ResolutionPreset { + low, + medium, + high, + veryHigh, + ultraHigh, + max, } - mediaRecorder = - new MediaRecorderBuilder(recordingProfile, outputFilePath) - .setEnableAudio(enableAudio) - .setMediaOrientation(getMediaOrientation()) - .build(); - } - - @SuppressLint("MissingPermission") - public void open(@NonNull final Result result) throws CameraAccessException { - pictureImageReader = - ImageReader.newInstance( - captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2); - - // Used to steam image byte data to dart side. - imageStreamReader = - ImageReader.newInstance( - previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2); - - cameraManager.openCamera( - cameraName, - new CameraDevice.StateCallback() { - @Override - public void onOpened(@NonNull CameraDevice device) { - cameraDevice = device; - try { - startPreview(); - } catch (CameraAccessException e) { - result.error("CameraAccess", e.getMessage(), null); - close(); - return; - } - Map reply = new HashMap<>(); - reply.put("textureId", flutterTexture.id()); - reply.put("previewWidth", previewSize.getWidth()); - reply.put("previewHeight", previewSize.getHeight()); - result.success(reply); - } - - @Override - public void onClosed(@NonNull CameraDevice camera) { - dartMessenger.sendCameraClosingEvent(); - super.onClosed(camera); - } - - @Override - public void onDisconnected(@NonNull CameraDevice cameraDevice) { - close(); - dartMessenger.send(DartMessenger.EventType.ERROR, "The camera was disconnected."); - } - - @Override - public void onError(@NonNull CameraDevice cameraDevice, int errorCode) { - close(); - String errorDescription; - switch (errorCode) { - case ERROR_CAMERA_IN_USE: - errorDescription = "The camera device is in use already."; - break; - case ERROR_MAX_CAMERAS_IN_USE: - errorDescription = "Max cameras in use"; - break; - case ERROR_CAMERA_DISABLED: - errorDescription = "The camera device could not be opened due to a device policy."; - break; - case ERROR_CAMERA_DEVICE: - errorDescription = "The camera device has encountered a fatal error"; - break; - case ERROR_CAMERA_SERVICE: - errorDescription = "The camera service has encountered a fatal error."; - break; - default: - errorDescription = "Unknown camera error"; - } - dartMessenger.send(DartMessenger.EventType.ERROR, errorDescription); - } - }, - null); - } - - private void writeToFile(ByteBuffer buffer, File file) throws IOException { - try (FileOutputStream outputStream = new FileOutputStream(file)) { - while (0 < buffer.remaining()) { - outputStream.getChannel().write(buffer); - } + public Camera( + final Activity activity, + final SurfaceTextureEntry flutterTexture, + final DartMessenger dartMessenger, + final String cameraName, + final String resolutionPreset, + final boolean enableAudio) + throws CameraAccessException { + if (activity == null) { + throw new IllegalStateException("No activity available!"); + } + this.cameraName = cameraName; + this.enableAudio = enableAudio; + this.flutterTexture = flutterTexture; + this.dartMessenger = dartMessenger; + this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); + this.applicationContext = activity.getApplicationContext(); + orientationEventListener = + new OrientationEventListener(activity.getApplicationContext()) { + @Override + public void onOrientationChanged(int i) { + if (i == ORIENTATION_UNKNOWN) { + return; + } + // Convert the raw deg angle to the nearest multiple of 90. + currentOrientation = (int) Math.round(i / 90.0) * 90; + } + }; + orientationEventListener.enable(); + + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); + StreamConfigurationMap streamConfigurationMap = + characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + //noinspection ConstantConditions + sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + //noinspection ConstantConditions + isFrontFacing = + characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT; + ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset); + recordingProfile = + CameraUtils.getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset); + captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); + previewSize = computeBestPreviewSize(cameraName, preset); } - } - SurfaceTextureEntry getFlutterTexture() { - return flutterTexture; - } + private void prepareMediaRecorder(String outputFilePath) throws IOException { + if (mediaRecorder != null) { + mediaRecorder.release(); + } - public void takePicture(String filePath, @NonNull final Result result) { - final File file = new File(filePath); + mediaRecorder = + new MediaRecorderBuilder(recordingProfile, outputFilePath) + .setEnableAudio(enableAudio) + .setMediaOrientation(getMediaOrientation()) + .build(); + } - if (file.exists()) { - result.error( - "fileExists", "File at path '" + filePath + "' already exists. Cannot overwrite.", null); - return; + @SuppressLint("MissingPermission") + public void open(@NonNull final Result result) throws CameraAccessException { + pictureImageReader = + ImageReader.newInstance( + captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2); + + // Used to steam image byte data to dart side. + imageStreamReader = + ImageReader.newInstance( + previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2); + + cameraManager.openCamera( + cameraName, + new CameraDevice.StateCallback() { + @Override + public void onOpened(@NonNull CameraDevice device) { + cameraDevice = device; + try { + startPreview(); + } catch (CameraAccessException e) { + result.error("CameraAccess", e.getMessage(), null); + close(); + return; + } + Map reply = new HashMap<>(); + reply.put("cameraId", flutterTexture.id()); + result.success(reply); + dartMessenger.sendResolutionChangedEvent(previewSize.getWidth(), previewSize.getHeight(), null, null); + } + + @Override + public void onClosed(@NonNull CameraDevice camera) { + dartMessenger.sendCameraClosingEvent(); + super.onClosed(camera); + } + + @Override + public void onDisconnected(@NonNull CameraDevice cameraDevice) { + close(); + dartMessenger.sendCameraErrorEvent("The camera was disconnected."); + } + + @Override + public void onError(@NonNull CameraDevice cameraDevice, int errorCode) { + close(); + String errorDescription; + switch (errorCode) { + case ERROR_CAMERA_IN_USE: + errorDescription = "The camera device is in use already."; + break; + case ERROR_MAX_CAMERAS_IN_USE: + errorDescription = "Max cameras in use"; + break; + case ERROR_CAMERA_DISABLED: + errorDescription = "The camera device could not be opened due to a device policy."; + break; + case ERROR_CAMERA_DEVICE: + errorDescription = "The camera device has encountered a fatal error"; + break; + case ERROR_CAMERA_SERVICE: + errorDescription = "The camera service has encountered a fatal error."; + break; + default: + errorDescription = "Unknown camera error"; + } + dartMessenger.sendCameraErrorEvent(errorDescription); + } + }, + null); } - pictureImageReader.setOnImageAvailableListener( - reader -> { - try (Image image = reader.acquireLatestImage()) { - ByteBuffer buffer = image.getPlanes()[0].getBuffer(); - writeToFile(buffer, file); - result.success(null); - } catch (IOException e) { - result.error("IOError", "Failed saving image", null); - } - }, - null); - - try { - final CaptureRequest.Builder captureBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); - captureBuilder.addTarget(pictureImageReader.getSurface()); - captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); - - cameraCaptureSession.capture( - captureBuilder.build(), - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureFailed( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull CaptureFailure failure) { - String reason; - switch (failure.getReason()) { - case CaptureFailure.REASON_ERROR: - reason = "An error happened in the framework"; - break; - case CaptureFailure.REASON_FLUSHED: - reason = "The capture has failed due to an abortCaptures() call"; - break; - default: - reason = "Unknown reason"; - } - result.error("captureFailure", reason, null); + private void writeToFile(ByteBuffer buffer, File file) throws IOException { + try (FileOutputStream outputStream = new FileOutputStream(file)) { + while (0 < buffer.remaining()) { + outputStream.getChannel().write(buffer); } - }, - null); - } catch (CameraAccessException e) { - result.error("cameraAccess", e.getMessage(), null); + } } - } - - private void createCaptureSession(int templateType, Surface... surfaces) - throws CameraAccessException { - createCaptureSession(templateType, null, surfaces); - } - - private void createCaptureSession( - int templateType, Runnable onSuccessCallback, Surface... surfaces) - throws CameraAccessException { - // Close any existing capture session. - closeCaptureSession(); - - // Create a new capture builder. - captureRequestBuilder = cameraDevice.createCaptureRequest(templateType); - - // Build Flutter surface to render to - SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture(); - surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); - Surface flutterSurface = new Surface(surfaceTexture); - captureRequestBuilder.addTarget(flutterSurface); - - List remainingSurfaces = Arrays.asList(surfaces); - if (templateType != CameraDevice.TEMPLATE_PREVIEW) { - // If it is not preview mode, add all surfaces as targets. - for (Surface surface : remainingSurfaces) { - captureRequestBuilder.addTarget(surface); - } + + SurfaceTextureEntry getFlutterTexture() { + return flutterTexture; } - // Prepare the callback - CameraCaptureSession.StateCallback callback = - new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(@NonNull CameraCaptureSession session) { - try { - if (cameraDevice == null) { - dartMessenger.send( - DartMessenger.EventType.ERROR, "The camera was closed during configuration."); - return; - } - cameraCaptureSession = session; - captureRequestBuilder.set( - CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); - cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); - if (onSuccessCallback != null) { - onSuccessCallback.run(); - } - } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) { - dartMessenger.send(DartMessenger.EventType.ERROR, e.getMessage()); - } - } - - @Override - public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { - dartMessenger.send( - DartMessenger.EventType.ERROR, "Failed to configure camera session."); - } - }; - - // Collect all surfaces we want to render to. - List surfaceList = new ArrayList<>(); - surfaceList.add(flutterSurface); - surfaceList.addAll(remainingSurfaces); - // Start the session - cameraDevice.createCaptureSession(surfaceList, callback, null); - } - - public void startVideoRecording(String filePath, Result result) { - if (new File(filePath).exists()) { - result.error("fileExists", "File at path '" + filePath + "' already exists.", null); - return; + public void takePicture(@NonNull final Result result) { + final File outputDir = applicationContext.getCacheDir(); + final File file; + try { + file = File.createTempFile("CAP", ".jpg", outputDir); + } catch (IOException e) { + result.error("cannotCreateFile", e.getMessage(), null); + return; + } + + pictureImageReader.setOnImageAvailableListener( + reader -> { + try (Image image = reader.acquireLatestImage()) { + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); + writeToFile(buffer, file); + result.success(file.getAbsolutePath()); + } catch (IOException e) { + result.error("IOError", "Failed saving image", null); + } + }, + null); + + try { + final CaptureRequest.Builder captureBuilder = + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); + captureBuilder.addTarget(pictureImageReader.getSurface()); + captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); + + cameraCaptureSession.capture( + captureBuilder.build(), + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureFailed( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureFailure failure) { + String reason; + switch (failure.getReason()) { + case CaptureFailure.REASON_ERROR: + reason = "An error happened in the framework"; + break; + case CaptureFailure.REASON_FLUSHED: + reason = "The capture has failed due to an abortCaptures() call"; + break; + default: + reason = "Unknown reason"; + } + result.error("captureFailure", reason, null); + } + }, + null); + } catch (CameraAccessException e) { + result.error("cameraAccess", e.getMessage(), null); + } } - try { - prepareMediaRecorder(filePath); - recordingVideo = true; - createCaptureSession( - CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); - result.success(null); - } catch (CameraAccessException | IOException e) { - result.error("videoRecordingFailed", e.getMessage(), null); + + private void createCaptureSession(int templateType, Surface... surfaces) + throws CameraAccessException { + createCaptureSession(templateType, null, surfaces); } - } - public void stopVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; + private void createCaptureSession( + int templateType, Runnable onSuccessCallback, Surface... surfaces) + throws CameraAccessException { + // Close any existing capture session. + closeCaptureSession(); + + // Create a new capture builder. + captureRequestBuilder = cameraDevice.createCaptureRequest(templateType); + + // Build Flutter surface to render to + SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture(); + surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); + Surface flutterSurface = new Surface(surfaceTexture); + captureRequestBuilder.addTarget(flutterSurface); + + List remainingSurfaces = Arrays.asList(surfaces); + if (templateType != CameraDevice.TEMPLATE_PREVIEW) { + // If it is not preview mode, add all surfaces as targets. + for (Surface surface : remainingSurfaces) { + captureRequestBuilder.addTarget(surface); + } + } + + // Prepare the callback + CameraCaptureSession.StateCallback callback = + new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(@NonNull CameraCaptureSession session) { + try { + if (cameraDevice == null) { + dartMessenger.sendCameraErrorEvent("The camera was closed during configuration."); + return; + } + cameraCaptureSession = session; + captureRequestBuilder.set( + CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); + cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); + if (onSuccessCallback != null) { + onSuccessCallback.run(); + } + } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) { + dartMessenger.sendCameraErrorEvent(e.getMessage()); + } + } + + @Override + public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { + dartMessenger.sendCameraErrorEvent("Failed to configure camera session."); + } + }; + + // Collect all surfaces we want to render to. + List surfaceList = new ArrayList<>(); + surfaceList.add(flutterSurface); + surfaceList.addAll(remainingSurfaces); + // Start the session + cameraDevice.createCaptureSession(surfaceList, callback, null); } - try { - recordingVideo = false; - mediaRecorder.stop(); - mediaRecorder.reset(); - startPreview(); - result.success(null); - } catch (CameraAccessException | IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); + public void startVideoRecording(Result result) { + final File outputDir = applicationContext.getCacheDir(); + final File file; + try { + file = File.createTempFile("REC", ".mp4", outputDir); + } catch (IOException e) { + result.error("cannotCreateFile", e.getMessage(), null); + return; + } + + try { + prepareMediaRecorder(file.getAbsolutePath()); + recordingVideo = true; + createCaptureSession( + CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); + result.success(file.getAbsolutePath()); + } catch (CameraAccessException | IOException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + } } - } - public void pauseVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; + public void stopVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; + } + + try { + recordingVideo = false; + mediaRecorder.stop(); + mediaRecorder.reset(); + startPreview(); + result.success(null); + } catch (CameraAccessException | IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + } } - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mediaRecorder.pause(); - } else { - result.error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); - return; - } - } catch (IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - return; + public void pauseVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; + } + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mediaRecorder.pause(); + } else { + result.error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); + return; + } + } catch (IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + return; + } + + result.success(null); } - result.success(null); - } + public void resumeVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; + } + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mediaRecorder.resume(); + } else { + result.error( + "videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); + return; + } + } catch (IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + return; + } - public void resumeVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; + result.success(null); } - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mediaRecorder.resume(); - } else { - result.error( - "videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); - return; - } - } catch (IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - return; + public void startPreview() throws CameraAccessException { + if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; + + createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); } - result.success(null); - } - - public void startPreview() throws CameraAccessException { - if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; - - createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); - } - - public void startPreviewWithImageStream(EventChannel imageStreamChannel) - throws CameraAccessException { - createCaptureSession(CameraDevice.TEMPLATE_RECORD, imageStreamReader.getSurface()); - - imageStreamChannel.setStreamHandler( - new EventChannel.StreamHandler() { - @Override - public void onListen(Object o, EventChannel.EventSink imageStreamSink) { - setImageStreamImageAvailableListener(imageStreamSink); - } - - @Override - public void onCancel(Object o) { - imageStreamReader.setOnImageAvailableListener(null, null); - } - }); - } - - private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) { - imageStreamReader.setOnImageAvailableListener( - reader -> { - Image img = reader.acquireLatestImage(); - if (img == null) return; - - List> planes = new ArrayList<>(); - for (Image.Plane plane : img.getPlanes()) { - ByteBuffer buffer = plane.getBuffer(); - - byte[] bytes = new byte[buffer.remaining()]; - buffer.get(bytes, 0, bytes.length); - - Map planeBuffer = new HashMap<>(); - planeBuffer.put("bytesPerRow", plane.getRowStride()); - planeBuffer.put("bytesPerPixel", plane.getPixelStride()); - planeBuffer.put("bytes", bytes); - - planes.add(planeBuffer); - } - - Map imageBuffer = new HashMap<>(); - imageBuffer.put("width", img.getWidth()); - imageBuffer.put("height", img.getHeight()); - imageBuffer.put("format", img.getFormat()); - imageBuffer.put("planes", planes); - - imageStreamSink.success(imageBuffer); - img.close(); - }, - null); - } - - private void closeCaptureSession() { - if (cameraCaptureSession != null) { - cameraCaptureSession.close(); - cameraCaptureSession = null; + public void startPreviewWithImageStream(EventChannel imageStreamChannel) + throws CameraAccessException { + createCaptureSession(CameraDevice.TEMPLATE_RECORD, imageStreamReader.getSurface()); + + imageStreamChannel.setStreamHandler( + new EventChannel.StreamHandler() { + @Override + public void onListen(Object o, EventChannel.EventSink imageStreamSink) { + setImageStreamImageAvailableListener(imageStreamSink); + } + + @Override + public void onCancel(Object o) { + imageStreamReader.setOnImageAvailableListener(null, null); + } + }); } - } - public void close() { - closeCaptureSession(); + private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) { + imageStreamReader.setOnImageAvailableListener( + reader -> { + Image img = reader.acquireLatestImage(); + if (img == null) return; + + List> planes = new ArrayList<>(); + for (Image.Plane plane : img.getPlanes()) { + ByteBuffer buffer = plane.getBuffer(); + + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes, 0, bytes.length); + + Map planeBuffer = new HashMap<>(); + planeBuffer.put("bytesPerRow", plane.getRowStride()); + planeBuffer.put("bytesPerPixel", plane.getPixelStride()); + planeBuffer.put("bytes", bytes); + + planes.add(planeBuffer); + } + + Map imageBuffer = new HashMap<>(); + imageBuffer.put("width", img.getWidth()); + imageBuffer.put("height", img.getHeight()); + imageBuffer.put("format", img.getFormat()); + imageBuffer.put("planes", planes); + + imageStreamSink.success(imageBuffer); + img.close(); + }, + null); + } - if (cameraDevice != null) { - cameraDevice.close(); - cameraDevice = null; + private void closeCaptureSession() { + if (cameraCaptureSession != null) { + cameraCaptureSession.close(); + cameraCaptureSession = null; + } } - if (pictureImageReader != null) { - pictureImageReader.close(); - pictureImageReader = null; + + public void close() { + closeCaptureSession(); + + if (cameraDevice != null) { + cameraDevice.close(); + cameraDevice = null; + } + if (pictureImageReader != null) { + pictureImageReader.close(); + pictureImageReader = null; + } + if (imageStreamReader != null) { + imageStreamReader.close(); + imageStreamReader = null; + } + if (mediaRecorder != null) { + mediaRecorder.reset(); + mediaRecorder.release(); + mediaRecorder = null; + } } - if (imageStreamReader != null) { - imageStreamReader.close(); - imageStreamReader = null; + + public void dispose() { + close(); + flutterTexture.release(); + orientationEventListener.disable(); } - if (mediaRecorder != null) { - mediaRecorder.reset(); - mediaRecorder.release(); - mediaRecorder = null; + + private int getMediaOrientation() { + final int sensorOrientationOffset = + (currentOrientation == ORIENTATION_UNKNOWN) + ? 0 + : (isFrontFacing) ? -currentOrientation : currentOrientation; + return (sensorOrientationOffset + sensorOrientation + 360) % 360; } - } - - public void dispose() { - close(); - flutterTexture.release(); - orientationEventListener.disable(); - } - - private int getMediaOrientation() { - final int sensorOrientationOffset = - (currentOrientation == ORIENTATION_UNKNOWN) - ? 0 - : (isFrontFacing) ? -currentOrientation : currentOrientation; - return (sensorOrientationOffset + sensorOrientation + 360) % 360; - } } 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 08b36cb2cb87..9f31fb77875c 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 @@ -1,51 +1,57 @@ package io.flutter.plugins.camera; import android.text.TextUtils; + import androidx.annotation.Nullable; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.EventChannel; + import java.util.HashMap; import java.util.Map; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodChannel; + class DartMessenger { - @Nullable private EventChannel.EventSink eventSink; - - enum EventType { - ERROR, - CAMERA_CLOSING, - } - - DartMessenger(BinaryMessenger messenger, long eventChannelId) { - new EventChannel(messenger, "flutter.io/cameraPlugin/camera" + eventChannelId) - .setStreamHandler( - new EventChannel.StreamHandler() { - @Override - public void onListen(Object arguments, EventChannel.EventSink sink) { - eventSink = sink; - } - - @Override - public void onCancel(Object arguments) { - eventSink = null; - } - }); - } - - void sendCameraClosingEvent() { - send(EventType.CAMERA_CLOSING, null); - } - - void send(EventType eventType, @Nullable String description) { - if (eventSink == null) { - return; + @Nullable + private MethodChannel channel; + + + enum EventType { + ERROR, + CAMERA_CLOSING, + RESOLUTION_CHANGED, + } + + DartMessenger(BinaryMessenger messenger, long cameraId) { + channel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); + } + + void sendResolutionChangedEvent(Integer previewWidth, Integer previewHeight, Integer captureWidth, Integer captureHeight) { + this.send(EventType.RESOLUTION_CHANGED, new HashMap() {{ + if (previewWidth != null) put("previewWidth", previewWidth); + if (previewHeight != null) put("previewHeight", previewHeight); + if (captureWidth != null) put("captureWidth", captureWidth); + if (captureHeight != null) put("captureHeight", captureHeight); + }}); + } + + void sendCameraClosingEvent() { + send(EventType.CAMERA_CLOSING); + } + + void sendCameraErrorEvent(@Nullable String description) { + this.send(EventType.ERROR, new HashMap() {{ + if (!TextUtils.isEmpty(description)) put("description", description); + }}); + } + + void send(EventType eventType) { + send(eventType, new HashMap<>()); } - Map event = new HashMap<>(); - event.put("eventType", eventType.toString().toLowerCase()); - // Only errors have a description. - if (eventType == EventType.ERROR && !TextUtils.isEmpty(description)) { - event.put("description", description); + void send(EventType eventType, Map args) { + if (channel == null) { + return; + } + channel.invokeMethod(eventType.toString().toLowerCase(), args); } - eventSink.success(event); - } } 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 132075555f26..b6fcf89ac362 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 @@ -69,12 +69,11 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) result.error(errCode, errDesc, null); } }); - break; } case "takePicture": { - camera.takePicture(call.argument("path"), result); + camera.takePicture(result); break; } case "prepareForVideoRecording": @@ -85,7 +84,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) } case "startVideoRecording": { - camera.startVideoRecording(call.argument("filePath"), result); + camera.startVideoRecording(result); break; } case "stopVideoRecording": diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 551efd65139a..e97ae19ea0df 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -190,7 +190,6 @@ class CameraController extends ValueNotifier { int _cameraId; bool _isDisposed = false; - StreamSubscription _eventSubscription; StreamSubscription _imageStreamSubscription; Completer _creatingCompleter; @@ -215,14 +214,22 @@ class CameraController extends ValueNotifier { description, resolutionPreset, enableAudio: enableAudio); - value = value.copyWith(isInitialized: true); + Size previewSize = await CameraPlatform.instance + .onResolutionChanged(_cameraId) + .take(1) + .map((event) => Size( + event.previewWidth.toDouble(), + event.previewHeight.toDouble(), + )) + .first; + + value = value.copyWith( + isInitialized: true, + previewSize: previewSize, + ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } - _eventSubscription = - EventChannel('flutter.io/cameraPlugin/camera$_cameraId') - .receiveBroadcastStream() - .listen(_listener); _creatingCompleter.complete(); return _creatingCompleter.future; } @@ -491,7 +498,6 @@ class CameraController extends ValueNotifier { if (_creatingCompleter != null) { await _creatingCompleter.future; await CameraPlatform.instance.dispose(_cameraId); - await _eventSubscription?.cancel(); } } } 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 da4d8a79a334..e5768e0ca4f1 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 @@ -49,7 +49,7 @@ class MethodChannelCamera extends CameraPlatform { ResolutionPreset resolutionPreset, { bool enableAudio, }) async { - int _textureId; + int _cameraId; try { final Map reply = await _channel.invokeMapMethod( @@ -62,28 +62,28 @@ class MethodChannelCamera extends CameraPlatform { 'enableAudio': enableAudio, }, ); - _textureId = reply['textureId']; + _cameraId = reply['cameraId']; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } - if (!_channels.containsKey(_textureId)) { + if (!_channels.containsKey(_cameraId)) { final channel = - MethodChannel('flutter.io/cameraPlugin/camera$_textureId'); + MethodChannel('flutter.io/cameraPlugin/camera$_cameraId'); channel.setMethodCallHandler( - (MethodCall call) => handleMethodCall(call, _textureId)); - _channels[_textureId] = channel; - _cameraResolutionChangedEventStreams[_textureId] = _events(_textureId) + (MethodCall call) => handleMethodCall(call, _cameraId)); + _channels[_cameraId] = channel; + _cameraResolutionChangedEventStreams[_cameraId] = _events(_cameraId) .whereType() .shareReplay(maxSize: 1); } - return _textureId; + return _cameraId; } @override Future dispose(int cameraId) async { await _channel.invokeMethod( 'dispose', - {'textureId': cameraId}, + {'cameraId': cameraId}, ); _channels.remove(cameraId); } @@ -107,7 +107,7 @@ class MethodChannelCamera extends CameraPlatform { Future takePicture(int cameraId) async { String path = await _channel.invokeMethod( 'takePicture', - {'textureId': cameraId}, + {'cameraId': cameraId}, ); return XFile(path); } @@ -120,7 +120,7 @@ class MethodChannelCamera extends CameraPlatform { Future startVideoRecording(int cameraId) async { String path = await _channel.invokeMethod( 'startVideoRecording', - {'textureId': cameraId}, + {'cameraId': cameraId}, ); return XFile(path); } @@ -129,7 +129,7 @@ class MethodChannelCamera extends CameraPlatform { Future stopVideoRecording(int cameraId) async { await _channel.invokeMethod( 'stopVideoRecording', - {'textureId': cameraId}, + {'cameraId': cameraId}, ); } @@ -137,14 +137,14 @@ class MethodChannelCamera extends CameraPlatform { Future pauseVideoRecording(int cameraId) => _channel.invokeMethod( 'pauseVideoRecording', - {'textureId': cameraId}, + {'cameraId': cameraId}, ); @override Future resumeVideoRecording(int cameraId) => _channel.invokeMethod( 'resumeVideoRecording', - {'textureId': cameraId}, + {'cameraId': cameraId}, ); @override @@ -188,7 +188,7 @@ class MethodChannelCamera extends CameraPlatform { @visibleForTesting Future handleMethodCall(MethodCall call, int cameraId) async { switch (call.method) { - case 'camera#resolutionChanged': + case 'resolution_changed': _cameraEventStreamController.add(ResolutionChangedEvent( cameraId, call.arguments['captureWidth'], @@ -197,12 +197,12 @@ class MethodChannelCamera extends CameraPlatform { call.arguments['previewHeight'], )); break; - case 'camera#closing': + case 'camera_closing': _cameraEventStreamController.add(CameraClosingEvent( cameraId, )); break; - case 'camera#error': + case 'error': _cameraEventStreamController.add(CameraErrorEvent( cameraId, call.arguments['description'], diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index f0ac45c239ec..e3cdd84fc432 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -24,7 +24,7 @@ void main() { MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'textureId': 1} + 'initialize': {'cameraId': 1} }); final camera = MethodChannelCamera(); @@ -53,8 +53,8 @@ void main() { MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'textureId': 1}, - 'dispose': {'textureId': 1} + 'initialize': {'cameraId': 1}, + 'dispose': {'cameraId': 1} }); final camera = MethodChannelCamera(); @@ -72,7 +72,7 @@ void main() { isNotNull, isMethodCall( 'dispose', - arguments: {'textureId': 1}, + arguments: {'cameraId': 1}, ), ]); }); @@ -85,7 +85,7 @@ void main() { MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'textureId': 1}, + 'initialize': {'cameraId': 1}, }, ); camera = MethodChannelCamera(); @@ -184,7 +184,7 @@ void main() { MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'textureId': 1}, + 'initialize': {'cameraId': 1}, }, ); camera = MethodChannelCamera(); @@ -237,7 +237,7 @@ void main() { // Assert expect(channel.log, [ isMethodCall('takePicture', arguments: { - 'textureId': cameraId, + 'cameraId': cameraId, }), ]); expect(file.path, '/test/path.jpg'); @@ -272,7 +272,7 @@ void main() { // Assert expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { - 'textureId': cameraId, + 'cameraId': cameraId, }), ]); expect(file.path, '/test/path.mkv'); @@ -291,7 +291,7 @@ void main() { // Assert expect(channel.log, [ isMethodCall('stopVideoRecording', arguments: { - 'textureId': cameraId, + 'cameraId': cameraId, }), ]); }); @@ -309,7 +309,7 @@ void main() { // Assert expect(channel.log, [ isMethodCall('pauseVideoRecording', arguments: { - 'textureId': cameraId, + 'cameraId': cameraId, }), ]); }); @@ -327,7 +327,7 @@ void main() { // Assert expect(channel.log, [ isMethodCall('resumeVideoRecording', arguments: { - 'textureId': cameraId, + 'cameraId': cameraId, }), ]); }); From 01fcfe7c4b9f9aa7e6d585fb8df72a7bb7e7c46e Mon Sep 17 00:00:00 2001 From: daniel Date: Thu, 19 Nov 2020 17:30:58 +0100 Subject: [PATCH 39/89] Fixed example app for Android. Removed isRecordingVideo and isStreamingImages from buildView method. --- packages/camera/camera/example/lib/main.dart | 5 ++++- packages/camera/camera/lib/camera.dart | 15 +-------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index a23135f64f71..529bb9b8f97b 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -321,7 +321,10 @@ class _CameraExampleHomeState extends State void onVideoRecordButtonPressed() { startVideoRecording().then((XFile file) { if (mounted) setState(() {}); - if (file != null) showInSnackBar('Saving video to ${file.path}'); + if (file != null) { + showInSnackBar('Saving video to ${file.path}'); + videoFile = file; + } }); } diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index e97ae19ea0df..40943f6b9a81 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -320,22 +320,9 @@ class CameraController extends ValueNotifier { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', - 'startImageStream was called on uninitialized CameraController.', + 'buildView() was called on uninitialized CameraController.', ); } - if (value.isRecordingVideo) { - throw CameraException( - 'A video recording is already started.', - 'startImageStream was called while a video is being recorded.', - ); - } - if (value.isStreamingImages) { - throw CameraException( - 'A camera has started streaming images.', - 'startImageStream was called while a camera was streaming images.', - ); - } - try { return CameraPlatform.instance.buildView(_cameraId); } on PlatformException catch (e) { From 918033e359df23958bb0fb7837795f6072818b31 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 20 Nov 2020 15:42:59 +0100 Subject: [PATCH 40/89] iOS implementation: Removed standard event channel. Added reverse method channel. Updated initialize method. Added resolution changed event. Updated error reporting to use new method channel. --- .../camera/camera/ios/Classes/CameraPlugin.m | 99 ++++++------------- 1 file changed, 28 insertions(+), 71 deletions(-) diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 525c1286717a..d9b38cd5a3bf 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -152,14 +152,13 @@ static ResolutionPreset getResolutionPresetForString(NSString *preset) { @interface FLTCam : NSObject + AVCaptureAudioDataOutputSampleBufferDelegate + > @property(readonly, nonatomic) int64_t textureId; @property(nonatomic, copy) void (^onFrameAvailable)(void); @property BOOL enableAudio; -@property(nonatomic) FlutterEventChannel *eventChannel; @property(nonatomic) FLTImageStreamHandler *imageStreamHandler; -@property(nonatomic) FlutterEventSink eventSink; +@property(nonatomic) FlutterMethodChannel *methodChannel; @property(readonly, nonatomic) AVCaptureSession *captureSession; @property(readonly, nonatomic) AVCaptureDevice *captureDevice; @property(readonly, nonatomic) AVCapturePhotoOutput *capturePhotoOutput API_AVAILABLE(ios(10)); @@ -347,10 +346,7 @@ - (void)captureOutput:(AVCaptureOutput *)output } } if (!CMSampleBufferDataIsReady(sampleBuffer)) { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : @"sample buffer is not ready. Skipping sample" - }); + [_methodChannel invokeMethod:@"error" arguments:@"sample buffer is not ready. Skipping sample"]; return; } if (_isStreamingImages) { @@ -414,10 +410,7 @@ - (void)captureOutput:(AVCaptureOutput *)output } if (_isRecording && !_isRecordingPaused) { if (_videoWriter.status == AVAssetWriterStatusFailed) { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : [NSString stringWithFormat:@"%@", _videoWriter.error] - }); + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; return; } @@ -500,20 +493,13 @@ - (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset CF_R - (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { if (_videoWriter.status != AVAssetWriterStatusWriting) { if (_videoWriter.status == AVAssetWriterStatusFailed) { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : [NSString stringWithFormat:@"%@", _videoWriter.error] - }); + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; } return; } if (_videoWriterInput.readyForMoreMediaData) { if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : - [NSString stringWithFormat:@"%@", @"Unable to write to video input"] - }); + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to video input"]]; } } } @@ -521,20 +507,13 @@ - (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { - (void)newAudioSample:(CMSampleBufferRef)sampleBuffer { if (_videoWriter.status != AVAssetWriterStatusWriting) { if (_videoWriter.status == AVAssetWriterStatusFailed) { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : [NSString stringWithFormat:@"%@", _videoWriter.error] - }); + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; } return; } if (_audioWriterInput.readyForMoreMediaData) { if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : - [NSString stringWithFormat:@"%@", @"Unable to write to audio input"] - }); + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to audio input"]]; } } } @@ -565,23 +544,10 @@ - (CVPixelBufferRef)copyPixelBuffer { return pixelBuffer; } -- (FlutterError *_Nullable)onCancelWithArguments:(id _Nullable)arguments { - _eventSink = nil; - // need to unregister stream handler when disposing the camera - [_eventChannel setStreamHandler:nil]; - return nil; -} - -- (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments - eventSink:(nonnull FlutterEventSink)events { - _eventSink = events; - return nil; -} - - (void)startVideoRecordingAtPath:(NSString *)path result:(FlutterResult)result { if (!_isRecording) { if (![self setupWriterForPath:path]) { - _eventSink(@{@"event" : @"error", @"errorDescription" : @"Setup Writer Failed"}); + [_methodChannel invokeMethod:@"error" arguments:@"Setup Writer Failed"]; return; } _isRecording = YES; @@ -592,7 +558,7 @@ - (void)startVideoRecordingAtPath:(NSString *)path result:(FlutterResult)result _audioIsDisconnected = NO; result(nil); } else { - _eventSink(@{@"event" : @"error", @"errorDescription" : @"Video is already recording!"}); + [_methodChannel invokeMethod:@"error" arguments:@"Video is already recording"]; } } @@ -604,10 +570,7 @@ - (void)stopVideoRecordingWithResult:(FlutterResult)result { if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { result(nil); } else { - self->_eventSink(@{ - @"event" : @"error", - @"errorDescription" : @"AVAssetWriter could not finish writing!" - }); + [self->_methodChannel invokeMethod:@"error" arguments:@"AVAssetWriter could not finish writing!"]; } }]; } @@ -641,8 +604,7 @@ - (void)startImageStreamWithMessenger:(NSObject *)messen _isStreamingImages = YES; } else { - _eventSink( - @{@"event" : @"error", @"errorDescription" : @"Images from camera are already streaming!"}); + [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are already streaming!"]; } } @@ -651,8 +613,7 @@ - (void)stopImageStream { _isStreamingImages = NO; _imageStreamHandler = nil; } else { - _eventSink( - @{@"event" : @"error", @"errorDescription" : @"Images from camera are not streaming!"}); + [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are not streaming!"]; } } @@ -672,7 +633,7 @@ - (BOOL)setupWriterForPath:(NSString *)path { error:&error]; NSParameterAssert(_videoWriter); if (error) { - _eventSink(@{@"event" : @"error", @"errorDescription" : error.description}); + [_methodChannel invokeMethod:@"error" arguments:error.description]; return NO; } NSDictionary *videoSettings = [NSDictionary @@ -726,7 +687,7 @@ - (void)setUpCaptureSessionForAudio { AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error]; if (error) { - _eventSink(@{@"event" : @"error", @"errorDescription" : error.description}); + [_methodChannel invokeMethod:@"error" arguments:error.description]; } // Setup the audio output. _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; @@ -738,10 +699,7 @@ - (void)setUpCaptureSessionForAudio { [_captureSession addOutput:_audioOutput]; _isAudioSetup = YES; } else { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : @"Unable to add Audio input/output to session capture" - }); + [_methodChannel invokeMethod:@"error" arguments:@"Unable to add Audio input/output to session capture"]; _isAudioSetup = NO; } } @@ -841,20 +799,19 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re cam.onFrameAvailable = ^{ [weakSelf.registry textureFrameAvailable:textureId]; }; - FlutterEventChannel *eventChannel = [FlutterEventChannel - eventChannelWithName:[NSString - stringWithFormat:@"flutter.io/cameraPlugin/cameraEvents%lld", - textureId] - binaryMessenger:_messenger]; - [eventChannel setStreamHandler:cam]; - cam.eventChannel = eventChannel; + FlutterMethodChannel *methodChannel = [FlutterMethodChannel + methodChannelWithName:[NSString + stringWithFormat:@"flutter.io/cameraPlugin/camera%lld", + textureId] + binaryMessenger:_messenger]; + cam.methodChannel = methodChannel; result(@{ - @"textureId" : @(textureId), + @"cameraId" : @(textureId), + }); + [methodChannel invokeMethod:@"resolution_changed" arguments:@{ @"previewWidth" : @(cam.previewSize.width), - @"previewHeight" : @(cam.previewSize.height), - @"captureWidth" : @(cam.captureSize.width), - @"captureHeight" : @(cam.captureSize.height), - }); + @"previewHeight" : @(cam.previewSize.height) + }]; [cam start]; } } else if ([@"startImageStream" isEqualToString:call.method]) { From f5c0615690f76c2d43c3d2c5c05cd3f671d79290 Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 23 Nov 2020 14:47:32 +0100 Subject: [PATCH 41/89] Added some first tests for camera/camera --- packages/camera/camera/pubspec.yaml | 1 + packages/camera/camera/test/camera_test.dart | 3 + .../camera/camera/test/camera_value_test.dart | 98 +++++++++++++++++++ .../test/method_channel_camera_test.dart | 20 ++-- 4 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 packages/camera/camera/test/camera_value_test.dart diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index a1c9f3c5843f..433edaa54783 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -19,6 +19,7 @@ dev_dependencies: flutter_driver: sdk: flutter pedantic: ^1.8.0 + mockito: ^4.1.3 flutter: plugin: diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index cc33b369f000..d2521617a4ed 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:camera/camera.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; void main() { group('camera', () { @@ -35,6 +36,8 @@ void main() { }); } +class MockCameraPlatform extends Mock implements CameraPlatform {} + class MockCameraDescription extends CameraDescription { @override CameraLensDirection get lensDirection => CameraLensDirection.back; diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart new file mode 100644 index 000000000000..34fb4ad13e88 --- /dev/null +++ b/packages/camera/camera/test/camera_value_test.dart @@ -0,0 +1,98 @@ +import 'dart:ui'; + +import 'package:camera/camera.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('camera_value', () { + test('Can be created', () { + var cameraValue = const CameraValue( + isInitialized: false, + errorDescription: null, + previewSize: Size(10, 10), + isRecordingPaused: false, + isRecordingVideo: false, + isTakingPicture: false, + isStreamingImages: false, + ); + + expect(cameraValue, isA()); + expect(cameraValue.isInitialized, isFalse); + expect(cameraValue.errorDescription, null); + expect(cameraValue.previewSize, Size(10, 10)); + expect(cameraValue.isRecordingPaused, isFalse); + expect(cameraValue.isRecordingVideo, isFalse); + expect(cameraValue.isTakingPicture, isFalse); + expect(cameraValue.isStreamingImages, isFalse); + }); + + test('Can be created as uninitialized', () { + var cameraValue = const CameraValue.uninitialized(); + + expect(cameraValue, isA()); + expect(cameraValue.isInitialized, isFalse); + expect(cameraValue.errorDescription, null); + expect(cameraValue.previewSize, null); + expect(cameraValue.isRecordingPaused, isFalse); + expect(cameraValue.isRecordingVideo, isFalse); + expect(cameraValue.isTakingPicture, isFalse); + expect(cameraValue.isStreamingImages, isFalse); + }); + + test('Can be copied with isInitialized', () { + var cv = const CameraValue.uninitialized(); + var cameraValue = cv.copyWith(isInitialized: true); + + expect(cameraValue, isA()); + expect(cameraValue.isInitialized, isTrue); + expect(cameraValue.errorDescription, null); + expect(cameraValue.previewSize, null); + expect(cameraValue.isRecordingPaused, isFalse); + expect(cameraValue.isRecordingVideo, isFalse); + expect(cameraValue.isTakingPicture, isFalse); + expect(cameraValue.isStreamingImages, isFalse); + }); + + test('Has aspectRatio after setting size', () { + var cv = const CameraValue.uninitialized(); + var cameraValue = + cv.copyWith(isInitialized: true, previewSize: Size(20, 10)); + + expect(cameraValue.aspectRatio, 0.5); + }); + + test('hasError is true after setting errorDescription', () { + var cv = const CameraValue.uninitialized(); + var cameraValue = cv.copyWith(errorDescription: 'error'); + + expect(cameraValue.hasError, isTrue); + expect(cameraValue.errorDescription, 'error'); + }); + + test('Recording paused is false when not recording', () { + var cv = const CameraValue.uninitialized(); + var cameraValue = cv.copyWith( + isInitialized: true, + isRecordingVideo: false, + isRecordingPaused: true); + + expect(cameraValue.isRecordingPaused, isFalse); + }); + + test('toString() works as expected', () { + var cameraValue = const CameraValue( + isInitialized: false, + errorDescription: null, + previewSize: Size(10, 10), + isRecordingPaused: false, + isRecordingVideo: false, + isTakingPicture: false, + isStreamingImages: false, + ); + + expect(cameraValue.toString(), + 'CameraValue(isRecordingVideo: false, isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false)'); + }); + }); +} diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index e3cdd84fc432..59d9bfb62e2e 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -107,16 +107,16 @@ void main() { final uhdEvent = ResolutionChangedEvent(cameraId, 3840, 2160, 1280, 720); await camera.handleMethodCall( - MethodCall('camera#resolutionChanged', fhdEvent.toJson()), + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#resolutionChanged', uhdEvent.toJson()), + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#resolutionChanged', fhdEvent.toJson()), + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#resolutionChanged', uhdEvent.toJson()), + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); // Assert @@ -138,11 +138,11 @@ void main() { // Emit test events final event = CameraClosingEvent(cameraId); await camera.handleMethodCall( - MethodCall('camera#closing', event.toJson()), cameraId); + MethodCall('camera_closing', event.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#closing', event.toJson()), cameraId); + MethodCall('camera_closing', event.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#closing', event.toJson()), cameraId); + MethodCall('camera_closing', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); @@ -161,11 +161,11 @@ void main() { // Emit test events final event = CameraErrorEvent(cameraId, 'Error Description'); await camera.handleMethodCall( - MethodCall('camera#error', event.toJson()), cameraId); + MethodCall('error', event.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#error', event.toJson()), cameraId); + MethodCall('error', event.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#error', event.toJson()), cameraId); + MethodCall('error', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); From 837cef72f41a16c8622dfa50039e495eb6ecab9e Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 26 Nov 2020 11:51:33 +0100 Subject: [PATCH 42/89] Started splitting initialize method --- .../camera/example/ios/Flutter/.last_build_id | 1 + .../example/ios/Flutter/Flutter.podspec | 18 + .../ios/Runner.xcodeproj/project.pbxproj | 22 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../camera/camera/ios/Classes/CameraPlugin.m | 1304 +++++++++-------- packages/camera/camera/lib/camera.dart | 2 +- .../lib/src/events/camera_event.dart | 42 +- .../method_channel/method_channel_camera.dart | 60 +- .../platform_interface/camera_platform.dart | 11 +- .../camera_platform_interface/pubspec.yaml | 4 +- .../test/camera_platform_interface_test.dart | 4 +- .../test/method_channel_camera_test.dart | 8 +- .../example/ios/Flutter/Flutter.podspec | 18 + .../ios/Runner.xcodeproj/project.pbxproj | 22 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + 15 files changed, 804 insertions(+), 728 deletions(-) create mode 100644 packages/camera/camera/example/ios/Flutter/.last_build_id create mode 100644 packages/camera/camera/example/ios/Flutter/Flutter.podspec create mode 100644 packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec create mode 100644 packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/camera/camera/example/ios/Flutter/.last_build_id b/packages/camera/camera/example/ios/Flutter/.last_build_id new file mode 100644 index 000000000000..003e79479ba4 --- /dev/null +++ b/packages/camera/camera/example/ios/Flutter/.last_build_id @@ -0,0 +1 @@ +ae2fda886f38c43aa68f58016de359e7 \ No newline at end of file diff --git a/packages/camera/camera/example/ios/Flutter/Flutter.podspec b/packages/camera/camera/example/ios/Flutter/Flutter.podspec new file mode 100644 index 000000000000..5ca30416bac0 --- /dev/null +++ b/packages/camera/camera/example/ios/Flutter/Flutter.podspec @@ -0,0 +1,18 @@ +# +# NOTE: This podspec is NOT to be published. It is only used as a local source! +# + +Pod::Spec.new do |s| + s.name = 'Flutter' + s.version = '1.0.0' + s.summary = 'High-performance, high-fidelity mobile apps.' + s.description = <<-DESC +Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. + DESC + s.homepage = 'https://flutter.io' + s.license = { :type => 'MIT' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } + s.ios.deployment_target = '8.0' + s.vendored_frameworks = 'Flutter.framework' +end diff --git a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj index 862ee64fb666..d51240a02c14 100644 --- a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,11 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 75201D617916C49BDEDF852A /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 620DDA07C00B5FF2F937CB5B /* libPods-Runner.a */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -28,8 +24,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -40,7 +34,6 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 483D985F075B951ADBAD218E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 620DDA07C00B5FF2F937CB5B /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -48,7 +41,6 @@ 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -63,8 +55,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 75201D617916C49BDEDF852A /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -83,9 +73,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -181,6 +169,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = 7624MWN53C; }; }; }; @@ -229,7 +218,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 3E30118C54AB12C3EB9EDF27 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -269,9 +258,12 @@ files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../Flutter/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -315,7 +307,6 @@ /* Begin XCBuildConfiguration section */ 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -372,7 +363,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -426,6 +416,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = 7624MWN53C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -447,6 +438,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = 7624MWN53C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index d9b38cd5a3bf..a722b65b1063 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -9,9 +9,9 @@ #import static FlutterError *getFlutterError(NSError *error) { - return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] - message:error.localizedDescription - details:error.domain]; + return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] + message:error.localizedDescription + details:error.domain]; } @interface FLTSavePhotoDelegate : NSObject @@ -28,132 +28,132 @@ @interface FLTImageStreamHandler : NSObject @implementation FLTImageStreamHandler - (FlutterError *_Nullable)onCancelWithArguments:(id _Nullable)arguments { - _eventSink = nil; - return nil; + _eventSink = nil; + return nil; } - (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { - _eventSink = events; - return nil; + _eventSink = events; + return nil; } @end @implementation FLTSavePhotoDelegate { - /// Used to keep the delegate alive until didFinishProcessingPhotoSampleBuffer. - FLTSavePhotoDelegate *selfReference; + /// Used to keep the delegate alive until didFinishProcessingPhotoSampleBuffer. + FLTSavePhotoDelegate *selfReference; } - initWithPath:(NSString *)path - result:(FlutterResult)result - motionManager:(CMMotionManager *)motionManager - cameraPosition:(AVCaptureDevicePosition)cameraPosition { - self = [super init]; - NSAssert(self, @"super init cannot be nil"); - _path = path; - _result = result; - _motionManager = motionManager; - _cameraPosition = cameraPosition; - selfReference = self; - return self; + result:(FlutterResult)result + motionManager:(CMMotionManager *)motionManager +cameraPosition:(AVCaptureDevicePosition)cameraPosition { + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _path = path; + _result = result; + _motionManager = motionManager; + _cameraPosition = cameraPosition; + selfReference = self; + return self; } - (void)captureOutput:(AVCapturePhotoOutput *)output - didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer - previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer - resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings - bracketSettings:(AVCaptureBracketedStillImageSettings *)bracketSettings - error:(NSError *)error API_AVAILABLE(ios(10)) { - selfReference = nil; - if (error) { - _result(getFlutterError(error)); - return; - } - NSData *data = [AVCapturePhotoOutput - JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer - previewPhotoSampleBuffer:previewPhotoSampleBuffer]; - UIImage *image = [UIImage imageWithCGImage:[UIImage imageWithData:data].CGImage - scale:1.0 - orientation:[self getImageRotation]]; - // TODO(sigurdm): Consider writing file asynchronously. - bool success = [UIImageJPEGRepresentation(image, 1.0) writeToFile:_path atomically:YES]; - if (!success) { - _result([FlutterError errorWithCode:@"IOError" message:@"Unable to write file" details:nil]); - return; - } - _result(nil); +didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer +previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer + resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings + bracketSettings:(AVCaptureBracketedStillImageSettings *)bracketSettings + error:(NSError *)error API_AVAILABLE(ios(10)) { + selfReference = nil; + if (error) { + _result(getFlutterError(error)); + return; + } + NSData *data = [AVCapturePhotoOutput + JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer + previewPhotoSampleBuffer:previewPhotoSampleBuffer]; + UIImage *image = [UIImage imageWithCGImage:[UIImage imageWithData:data].CGImage + scale:1.0 + orientation:[self getImageRotation]]; + // TODO(sigurdm): Consider writing file asynchronously. + bool success = [UIImageJPEGRepresentation(image, 1.0) writeToFile:_path atomically:YES]; + if (!success) { + _result([FlutterError errorWithCode:@"IOError" message:@"Unable to write file" details:nil]); + return; + } + _result(nil); } - (UIImageOrientation)getImageRotation { - float const threshold = 45.0; - BOOL (^isNearValue)(float value1, float value2) = ^BOOL(float value1, float value2) { - return fabsf(value1 - value2) < threshold; - }; - BOOL (^isNearValueABS)(float value1, float value2) = ^BOOL(float value1, float value2) { - return isNearValue(fabsf(value1), fabsf(value2)); - }; - float yxAtan = (atan2(_motionManager.accelerometerData.acceleration.y, - _motionManager.accelerometerData.acceleration.x)) * - 180 / M_PI; - if (isNearValue(-90.0, yxAtan)) { - return UIImageOrientationRight; - } else if (isNearValueABS(180.0, yxAtan)) { - return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationUp - : UIImageOrientationDown; - } else if (isNearValueABS(0.0, yxAtan)) { - return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationDown /*rotate 180* */ - : UIImageOrientationUp /*do not rotate*/; - } else if (isNearValue(90.0, yxAtan)) { - return UIImageOrientationLeft; - } - // If none of the above, then the device is likely facing straight down or straight up -- just - // pick something arbitrary - // TODO: Maybe use the UIInterfaceOrientation if in these scenarios - return UIImageOrientationUp; + float const threshold = 45.0; + BOOL (^isNearValue)(float value1, float value2) = ^BOOL(float value1, float value2) { + return fabsf(value1 - value2) < threshold; + }; + BOOL (^isNearValueABS)(float value1, float value2) = ^BOOL(float value1, float value2) { + return isNearValue(fabsf(value1), fabsf(value2)); + }; + float yxAtan = (atan2(_motionManager.accelerometerData.acceleration.y, + _motionManager.accelerometerData.acceleration.x)) * + 180 / M_PI; + if (isNearValue(-90.0, yxAtan)) { + return UIImageOrientationRight; + } else if (isNearValueABS(180.0, yxAtan)) { + return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationUp + : UIImageOrientationDown; + } else if (isNearValueABS(0.0, yxAtan)) { + return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationDown /*rotate 180* */ + : UIImageOrientationUp /*do not rotate*/; + } else if (isNearValue(90.0, yxAtan)) { + return UIImageOrientationLeft; + } + // If none of the above, then the device is likely facing straight down or straight up -- just + // pick something arbitrary + // TODO: Maybe use the UIInterfaceOrientation if in these scenarios + return UIImageOrientationUp; } @end // Mirrors ResolutionPreset in camera.dart typedef enum { - veryLow, - low, - medium, - high, - veryHigh, - ultraHigh, - max, + veryLow, + low, + medium, + high, + veryHigh, + ultraHigh, + max, } ResolutionPreset; static ResolutionPreset getResolutionPresetForString(NSString *preset) { - if ([preset isEqualToString:@"veryLow"]) { - return veryLow; - } else if ([preset isEqualToString:@"low"]) { - return low; - } else if ([preset isEqualToString:@"medium"]) { - return medium; - } else if ([preset isEqualToString:@"high"]) { - return high; - } else if ([preset isEqualToString:@"veryHigh"]) { - return veryHigh; - } else if ([preset isEqualToString:@"ultraHigh"]) { - return ultraHigh; - } else if ([preset isEqualToString:@"max"]) { - return max; - } else { - NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain - code:NSURLErrorUnknown - userInfo:@{ - NSLocalizedDescriptionKey : [NSString - stringWithFormat:@"Unknown resolution preset %@", preset] - }]; - @throw error; - } + if ([preset isEqualToString:@"veryLow"]) { + return veryLow; + } else if ([preset isEqualToString:@"low"]) { + return low; + } else if ([preset isEqualToString:@"medium"]) { + return medium; + } else if ([preset isEqualToString:@"high"]) { + return high; + } else if ([preset isEqualToString:@"veryHigh"]) { + return veryHigh; + } else if ([preset isEqualToString:@"ultraHigh"]) { + return ultraHigh; + } else if ([preset isEqualToString:@"max"]) { + return max; + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown resolution preset %@", preset] + }]; + @throw error; + } } @interface FLTCam : NSObject +AVCaptureVideoDataOutputSampleBufferDelegate, +AVCaptureAudioDataOutputSampleBufferDelegate +> @property(readonly, nonatomic) int64_t textureId; @property(nonatomic, copy) void (^onFrameAvailable)(void); @property BOOL enableAudio; @@ -189,7 +189,7 @@ @interface FLTCam : NSObject 0) { - currentSampleTime = CMTimeAdd(currentSampleTime, dur); - } - - if (_audioIsDisconnected) { - _audioIsDisconnected = NO; - - if (_audioTimeOffset.value == 0) { - _audioTimeOffset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); + + CFRetain(sampleBuffer); + CMTime currentSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); + + if (_videoWriter.status != AVAssetWriterStatusWriting) { + [_videoWriter startWriting]; + [_videoWriter startSessionAtSourceTime:currentSampleTime]; + } + + if (output == _captureVideoOutput) { + if (_videoIsDisconnected) { + _videoIsDisconnected = NO; + + if (_videoTimeOffset.value == 0) { + _videoTimeOffset = CMTimeSubtract(currentSampleTime, _lastVideoSampleTime); + } else { + CMTime offset = CMTimeSubtract(currentSampleTime, _lastVideoSampleTime); + _videoTimeOffset = CMTimeAdd(_videoTimeOffset, offset); + } + + return; + } + + _lastVideoSampleTime = currentSampleTime; + + CVPixelBufferRef nextBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + CMTime nextSampleTime = CMTimeSubtract(_lastVideoSampleTime, _videoTimeOffset); + [_videoAdaptor appendPixelBuffer:nextBuffer withPresentationTime:nextSampleTime]; } else { - CMTime offset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); - _audioTimeOffset = CMTimeAdd(_audioTimeOffset, offset); + CMTime dur = CMSampleBufferGetDuration(sampleBuffer); + + if (dur.value > 0) { + currentSampleTime = CMTimeAdd(currentSampleTime, dur); + } + + if (_audioIsDisconnected) { + _audioIsDisconnected = NO; + + if (_audioTimeOffset.value == 0) { + _audioTimeOffset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); + } else { + CMTime offset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); + _audioTimeOffset = CMTimeAdd(_audioTimeOffset, offset); + } + + return; + } + + _lastAudioSampleTime = currentSampleTime; + + if (_audioTimeOffset.value != 0) { + CFRelease(sampleBuffer); + sampleBuffer = [self adjustTime:sampleBuffer by:_audioTimeOffset]; + } + + [self newAudioSample:sampleBuffer]; } - - return; - } - - _lastAudioSampleTime = currentSampleTime; - - if (_audioTimeOffset.value != 0) { + CFRelease(sampleBuffer); - sampleBuffer = [self adjustTime:sampleBuffer by:_audioTimeOffset]; - } - - [self newAudioSample:sampleBuffer]; } - - CFRelease(sampleBuffer); - } } - (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset CF_RETURNS_RETAINED { - CMItemCount count; - CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count); - CMSampleTimingInfo *pInfo = malloc(sizeof(CMSampleTimingInfo) * count); - CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count); - for (CMItemCount i = 0; i < count; i++) { - pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset); - pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset); - } - CMSampleBufferRef sout; - CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout); - free(pInfo); - return sout; + CMItemCount count; + CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count); + CMSampleTimingInfo *pInfo = malloc(sizeof(CMSampleTimingInfo) * count); + CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count); + for (CMItemCount i = 0; i < count; i++) { + pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset); + pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset); + } + CMSampleBufferRef sout; + CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout); + free(pInfo); + return sout; } - (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { - if (_videoWriter.status != AVAssetWriterStatusWriting) { - if (_videoWriter.status == AVAssetWriterStatusFailed) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; + if (_videoWriter.status != AVAssetWriterStatusWriting) { + if (_videoWriter.status == AVAssetWriterStatusFailed) { + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; + } + return; } - return; - } - if (_videoWriterInput.readyForMoreMediaData) { - if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to video input"]]; + if (_videoWriterInput.readyForMoreMediaData) { + if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to video input"]]; + } } - } } - (void)newAudioSample:(CMSampleBufferRef)sampleBuffer { - if (_videoWriter.status != AVAssetWriterStatusWriting) { - if (_videoWriter.status == AVAssetWriterStatusFailed) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; + if (_videoWriter.status != AVAssetWriterStatusWriting) { + if (_videoWriter.status == AVAssetWriterStatusFailed) { + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; + } + return; } - return; - } - if (_audioWriterInput.readyForMoreMediaData) { - if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to audio input"]]; + if (_audioWriterInput.readyForMoreMediaData) { + if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { + [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to audio input"]]; + } } - } } - (void)close { - [_captureSession stopRunning]; - for (AVCaptureInput *input in [_captureSession inputs]) { - [_captureSession removeInput:input]; - } - for (AVCaptureOutput *output in [_captureSession outputs]) { - [_captureSession removeOutput:output]; - } + [_captureSession stopRunning]; + for (AVCaptureInput *input in [_captureSession inputs]) { + [_captureSession removeInput:input]; + } + for (AVCaptureOutput *output in [_captureSession outputs]) { + [_captureSession removeOutput:output]; + } } - (void)dealloc { - if (_latestPixelBuffer) { - CFRelease(_latestPixelBuffer); - } - [_motionManager stopAccelerometerUpdates]; + if (_latestPixelBuffer) { + CFRelease(_latestPixelBuffer); + } + [_motionManager stopAccelerometerUpdates]; } - (CVPixelBufferRef)copyPixelBuffer { - CVPixelBufferRef pixelBuffer = _latestPixelBuffer; - while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) { - pixelBuffer = _latestPixelBuffer; - } - - return pixelBuffer; + CVPixelBufferRef pixelBuffer = _latestPixelBuffer; + while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) { + pixelBuffer = _latestPixelBuffer; + } + + return pixelBuffer; } - (void)startVideoRecordingAtPath:(NSString *)path result:(FlutterResult)result { - if (!_isRecording) { - if (![self setupWriterForPath:path]) { - [_methodChannel invokeMethod:@"error" arguments:@"Setup Writer Failed"]; - return; + if (!_isRecording) { + if (![self setupWriterForPath:path]) { + [_methodChannel invokeMethod:@"error" arguments:@"Setup Writer Failed"]; + return; + } + _isRecording = YES; + _isRecordingPaused = NO; + _videoTimeOffset = CMTimeMake(0, 1); + _audioTimeOffset = CMTimeMake(0, 1); + _videoIsDisconnected = NO; + _audioIsDisconnected = NO; + result(nil); + } else { + [_methodChannel invokeMethod:@"error" arguments:@"Video is already recording"]; } - _isRecording = YES; - _isRecordingPaused = NO; - _videoTimeOffset = CMTimeMake(0, 1); - _audioTimeOffset = CMTimeMake(0, 1); - _videoIsDisconnected = NO; - _audioIsDisconnected = NO; - result(nil); - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Video is already recording"]; - } } - (void)stopVideoRecordingWithResult:(FlutterResult)result { - if (_isRecording) { - _isRecording = NO; - if (_videoWriter.status != AVAssetWriterStatusUnknown) { - [_videoWriter finishWritingWithCompletionHandler:^{ - if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { - result(nil); - } else { - [self->_methodChannel invokeMethod:@"error" arguments:@"AVAssetWriter could not finish writing!"]; + if (_isRecording) { + _isRecording = NO; + if (_videoWriter.status != AVAssetWriterStatusUnknown) { + [_videoWriter finishWritingWithCompletionHandler:^{ + if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { + result(nil); + } else { + [self->_methodChannel invokeMethod:@"error" arguments:@"AVAssetWriter could not finish writing!"]; + } + }]; } - }]; - } - } else { - NSError *error = + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorResourceUnavailable userInfo:@{NSLocalizedDescriptionKey : @"Video is not recording!"}]; - result(getFlutterError(error)); - } + result(getFlutterError(error)); + } } - (void)pauseVideoRecording { - _isRecordingPaused = YES; - _videoIsDisconnected = YES; - _audioIsDisconnected = YES; + _isRecordingPaused = YES; + _videoIsDisconnected = YES; + _audioIsDisconnected = YES; } - (void)resumeVideoRecording { - _isRecordingPaused = NO; + _isRecordingPaused = NO; } - (void)startImageStreamWithMessenger:(NSObject *)messenger { - if (!_isStreamingImages) { - FlutterEventChannel *eventChannel = + if (!_isStreamingImages) { + FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:@"plugins.flutter.io/camera/imageStream" binaryMessenger:messenger]; - - _imageStreamHandler = [[FLTImageStreamHandler alloc] init]; - [eventChannel setStreamHandler:_imageStreamHandler]; - - _isStreamingImages = YES; - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are already streaming!"]; - } + + _imageStreamHandler = [[FLTImageStreamHandler alloc] init]; + [eventChannel setStreamHandler:_imageStreamHandler]; + + _isStreamingImages = YES; + } else { + [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are already streaming!"]; + } } - (void)stopImageStream { - if (_isStreamingImages) { - _isStreamingImages = NO; - _imageStreamHandler = nil; - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are not streaming!"]; - } + if (_isStreamingImages) { + _isStreamingImages = NO; + _imageStreamHandler = nil; + } else { + [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are not streaming!"]; + } } - (BOOL)setupWriterForPath:(NSString *)path { - NSError *error = nil; - NSURL *outputURL; - if (path != nil) { - outputURL = [NSURL fileURLWithPath:path]; - } else { - return NO; - } - if (_enableAudio && !_isAudioSetup) { - [self setUpCaptureSessionForAudio]; - } - _videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL - fileType:AVFileTypeQuickTimeMovie - error:&error]; - NSParameterAssert(_videoWriter); - if (error) { - [_methodChannel invokeMethod:@"error" arguments:error.description]; - return NO; - } - NSDictionary *videoSettings = [NSDictionary - dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey, + NSError *error = nil; + NSURL *outputURL; + if (path != nil) { + outputURL = [NSURL fileURLWithPath:path]; + } else { + return NO; + } + if (_enableAudio && !_isAudioSetup) { + [self setUpCaptureSessionForAudio]; + } + _videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL + fileType:AVFileTypeQuickTimeMovie + error:&error]; + NSParameterAssert(_videoWriter); + if (error) { + [_methodChannel invokeMethod:@"error" arguments:error.description]; + return NO; + } + NSDictionary *videoSettings = [NSDictionary + dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey, [NSNumber numberWithInt:_previewSize.height], AVVideoWidthKey, [NSNumber numberWithInt:_previewSize.width], AVVideoHeightKey, nil]; - _videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo - outputSettings:videoSettings]; - - _videoAdaptor = [AVAssetWriterInputPixelBufferAdaptor - assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_videoWriterInput - sourcePixelBufferAttributes:@{ - (NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat) - }]; - - NSParameterAssert(_videoWriterInput); - _videoWriterInput.expectsMediaDataInRealTime = YES; - - // Add the audio input - if (_enableAudio) { - AudioChannelLayout acl; - bzero(&acl, sizeof(acl)); - acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; - NSDictionary *audioOutputSettings = nil; - // Both type of audio inputs causes output video file to be corrupted. - audioOutputSettings = [NSDictionary - dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kAudioFormatMPEG4AAC], AVFormatIDKey, - [NSNumber numberWithFloat:44100.0], AVSampleRateKey, - [NSNumber numberWithInt:1], AVNumberOfChannelsKey, - [NSData dataWithBytes:&acl length:sizeof(acl)], - AVChannelLayoutKey, nil]; - _audioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio - outputSettings:audioOutputSettings]; - _audioWriterInput.expectsMediaDataInRealTime = YES; - - [_videoWriter addInput:_audioWriterInput]; - [_audioOutput setSampleBufferDelegate:self queue:_dispatchQueue]; - } - - [_videoWriter addInput:_videoWriterInput]; - [_captureVideoOutput setSampleBufferDelegate:self queue:_dispatchQueue]; - - return YES; + _videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo + outputSettings:videoSettings]; + + _videoAdaptor = [AVAssetWriterInputPixelBufferAdaptor + assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_videoWriterInput + sourcePixelBufferAttributes:@{ + (NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat) + }]; + + NSParameterAssert(_videoWriterInput); + _videoWriterInput.expectsMediaDataInRealTime = YES; + + // Add the audio input + if (_enableAudio) { + AudioChannelLayout acl; + bzero(&acl, sizeof(acl)); + acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; + NSDictionary *audioOutputSettings = nil; + // Both type of audio inputs causes output video file to be corrupted. + audioOutputSettings = [NSDictionary + dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kAudioFormatMPEG4AAC], AVFormatIDKey, + [NSNumber numberWithFloat:44100.0], AVSampleRateKey, + [NSNumber numberWithInt:1], AVNumberOfChannelsKey, + [NSData dataWithBytes:&acl length:sizeof(acl)], + AVChannelLayoutKey, nil]; + _audioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio + outputSettings:audioOutputSettings]; + _audioWriterInput.expectsMediaDataInRealTime = YES; + + [_videoWriter addInput:_audioWriterInput]; + [_audioOutput setSampleBufferDelegate:self queue:_dispatchQueue]; + } + + [_videoWriter addInput:_videoWriterInput]; + [_captureVideoOutput setSampleBufferDelegate:self queue:_dispatchQueue]; + + return YES; } - (void)setUpCaptureSessionForAudio { - NSError *error = nil; - // Create a device input with the device and add it to the session. - // Setup the audio input. - AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; - AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice - error:&error]; - if (error) { - [_methodChannel invokeMethod:@"error" arguments:error.description]; - } - // Setup the audio output. - _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; - - if ([_captureSession canAddInput:audioInput]) { - [_captureSession addInput:audioInput]; - - if ([_captureSession canAddOutput:_audioOutput]) { - [_captureSession addOutput:_audioOutput]; - _isAudioSetup = YES; - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Unable to add Audio input/output to session capture"]; - _isAudioSetup = NO; + NSError *error = nil; + // Create a device input with the device and add it to the session. + // Setup the audio input. + AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; + AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice + error:&error]; + if (error) { + [_methodChannel invokeMethod:@"error" arguments:error.description]; + } + // Setup the audio output. + _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; + + if ([_captureSession canAddInput:audioInput]) { + [_captureSession addInput:audioInput]; + + if ([_captureSession canAddOutput:_audioOutput]) { + [_captureSession addOutput:_audioOutput]; + _isAudioSetup = YES; + } else { + [_methodChannel invokeMethod:@"error" arguments:@"Unable to add Audio input/output to session capture"]; + _isAudioSetup = NO; + } } - } } @end @@ -713,144 +713,152 @@ @interface CameraPlugin () @end @implementation CameraPlugin { - dispatch_queue_t _dispatchQueue; + dispatch_queue_t _dispatchQueue; } + (void)registerWithRegistrar:(NSObject *)registrar { - FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera" - binaryMessenger:[registrar messenger]]; - CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] - messenger:[registrar messenger]]; - [registrar addMethodCallDelegate:instance channel:channel]; + FlutterMethodChannel *channel = + [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera" + binaryMessenger:[registrar messenger]]; + CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] + messenger:[registrar messenger]]; + [registrar addMethodCallDelegate:instance channel:channel]; } - (instancetype)initWithRegistry:(NSObject *)registry messenger:(NSObject *)messenger { - self = [super init]; - NSAssert(self, @"super init cannot be nil"); - _registry = registry; - _messenger = messenger; - return self; + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _registry = registry; + _messenger = messenger; + return self; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if (_dispatchQueue == nil) { - _dispatchQueue = dispatch_queue_create("io.flutter.camera.dispatchqueue", NULL); - } - - // Invoke the plugin on another dispatch queue to avoid blocking the UI. - dispatch_async(_dispatchQueue, ^{ - [self handleMethodCallAsync:call result:result]; - }); + if (_dispatchQueue == nil) { + _dispatchQueue = dispatch_queue_create("io.flutter.camera.dispatchqueue", NULL); + } + + // Invoke the plugin on another dispatch queue to avoid blocking the UI. + dispatch_async(_dispatchQueue, ^{ + [self handleMethodCallAsync:call result:result]; + }); } - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([@"availableCameras" isEqualToString:call.method]) { - if (@available(iOS 10.0, *)) { - AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession - discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ] - mediaType:AVMediaTypeVideo - position:AVCaptureDevicePositionUnspecified]; - NSArray *devices = discoverySession.devices; - NSMutableArray *> *reply = - [[NSMutableArray alloc] initWithCapacity:devices.count]; - for (AVCaptureDevice *device in devices) { - NSString *lensFacing; - switch ([device position]) { - case AVCaptureDevicePositionBack: - lensFacing = @"back"; - break; - case AVCaptureDevicePositionFront: - lensFacing = @"front"; - break; - case AVCaptureDevicePositionUnspecified: - lensFacing = @"external"; - break; + if ([@"availableCameras" isEqualToString:call.method]) { + if (@available(iOS 10.0, *)) { + AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession + discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ] + mediaType:AVMediaTypeVideo + position:AVCaptureDevicePositionUnspecified]; + NSArray *devices = discoverySession.devices; + NSMutableArray *> *reply = + [[NSMutableArray alloc] initWithCapacity:devices.count]; + for (AVCaptureDevice *device in devices) { + NSString *lensFacing; + switch ([device position]) { + case AVCaptureDevicePositionBack: + lensFacing = @"back"; + break; + case AVCaptureDevicePositionFront: + lensFacing = @"front"; + break; + case AVCaptureDevicePositionUnspecified: + lensFacing = @"external"; + break; + } + [reply addObject:@{ + @"name" : [device uniqueID], + @"lensFacing" : lensFacing, + @"sensorOrientation" : @90, + }]; + } + result(reply); + } else { + result(FlutterMethodNotImplemented); } - [reply addObject:@{ - @"name" : [device uniqueID], - @"lensFacing" : lensFacing, - @"sensorOrientation" : @90, + } else if ([@"create" isEqualToString:call.method]) { + NSString *cameraName = call.arguments[@"cameraName"]; + NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; + NSNumber *enableAudio = call.arguments[@"enableAudio"]; + NSError *error; + FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName + resolutionPreset:resolutionPreset + enableAudio:[enableAudio boolValue] + dispatchQueue:_dispatchQueue + error:&error]; + + if (error) { + result(getFlutterError(error)); + } else { + if (_camera) { + [_camera close]; + } + int64_t textureId = [_registry registerTexture:cam]; + _camera = cam; + + result(@{ + @"cameraId" : @(textureId), + }); + } + } else if ([@"initialize" isEqualToString:call.method]) { + NSNumber* cameraIdArg = call.arguments[@"cameraId"]; + int64_t cameraId = [cameraIdArg longLongValue]; + + __weak CameraPlugin *weakSelf = self; + _camera.onFrameAvailable = ^{ + [weakSelf.registry textureFrameAvailable:cameraId]; + }; + + FlutterMethodChannel *methodChannel = [FlutterMethodChannel + methodChannelWithName:[NSString + stringWithFormat:@"flutter.io/cameraPlugin/camera%lld", + cameraId] + binaryMessenger:_messenger]; + _camera.methodChannel = methodChannel; + [methodChannel invokeMethod:@"initialized" arguments:@{ + @"previewWidth" : @(_camera.previewSize.width), + @"previewHeight" : @(_camera.previewSize.height) }]; - } - result(reply); - } else { - result(FlutterMethodNotImplemented); - } - } else if ([@"initialize" isEqualToString:call.method]) { - NSString *cameraName = call.arguments[@"cameraName"]; - NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; - NSNumber *enableAudio = call.arguments[@"enableAudio"]; - NSError *error; - FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName - resolutionPreset:resolutionPreset - enableAudio:[enableAudio boolValue] - dispatchQueue:_dispatchQueue - error:&error]; - if (error) { - result(getFlutterError(error)); + [_camera start]; + result(nil); + } else if ([@"startImageStream" isEqualToString:call.method]) { + [_camera startImageStreamWithMessenger:_messenger]; + result(nil); + } else if ([@"stopImageStream" isEqualToString:call.method]) { + [_camera stopImageStream]; + result(nil); + } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { + [_camera pauseVideoRecording]; + result(nil); + } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { + [_camera resumeVideoRecording]; + result(nil); } else { - if (_camera) { - [_camera close]; - } - int64_t textureId = [_registry registerTexture:cam]; - _camera = cam; - __weak CameraPlugin *weakSelf = self; - cam.onFrameAvailable = ^{ - [weakSelf.registry textureFrameAvailable:textureId]; - }; - FlutterMethodChannel *methodChannel = [FlutterMethodChannel - methodChannelWithName:[NSString - stringWithFormat:@"flutter.io/cameraPlugin/camera%lld", - textureId] - binaryMessenger:_messenger]; - cam.methodChannel = methodChannel; - result(@{ - @"cameraId" : @(textureId), - }); - [methodChannel invokeMethod:@"resolution_changed" arguments:@{ - @"previewWidth" : @(cam.previewSize.width), - @"previewHeight" : @(cam.previewSize.height) - }]; - [cam start]; - } - } else if ([@"startImageStream" isEqualToString:call.method]) { - [_camera startImageStreamWithMessenger:_messenger]; - result(nil); - } else if ([@"stopImageStream" isEqualToString:call.method]) { - [_camera stopImageStream]; - result(nil); - } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { - [_camera pauseVideoRecording]; - result(nil); - } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { - [_camera resumeVideoRecording]; - result(nil); - } else { - NSDictionary *argsMap = call.arguments; - NSUInteger textureId = ((NSNumber *)argsMap[@"textureId"]).unsignedIntegerValue; - if ([@"takePicture" isEqualToString:call.method]) { - if (@available(iOS 10.0, *)) { - [_camera captureToFile:call.arguments[@"path"] result:result]; - } else { - result(FlutterMethodNotImplemented); - } - } else if ([@"dispose" isEqualToString:call.method]) { - [_registry unregisterTexture:textureId]; - [_camera close]; - _dispatchQueue = nil; - result(nil); - } else if ([@"prepareForVideoRecording" isEqualToString:call.method]) { - [_camera setUpCaptureSessionForAudio]; - result(nil); - } else if ([@"startVideoRecording" isEqualToString:call.method]) { - [_camera startVideoRecordingAtPath:call.arguments[@"filePath"] result:result]; - } else if ([@"stopVideoRecording" isEqualToString:call.method]) { - [_camera stopVideoRecordingWithResult:result]; - } else { - result(FlutterMethodNotImplemented); + NSDictionary *argsMap = call.arguments; + NSUInteger textureId = ((NSNumber *)argsMap[@"textureId"]).unsignedIntegerValue; + if ([@"takePicture" isEqualToString:call.method]) { + if (@available(iOS 10.0, *)) { + [_camera captureToFile:call.arguments[@"path"] result:result]; + } else { + result(FlutterMethodNotImplemented); + } + } else if ([@"dispose" isEqualToString:call.method]) { + [_registry unregisterTexture:textureId]; + [_camera close]; + _dispatchQueue = nil; + result(nil); + } else if ([@"prepareForVideoRecording" isEqualToString:call.method]) { + [_camera setUpCaptureSessionForAudio]; + result(nil); + } else if ([@"startVideoRecording" isEqualToString:call.method]) { + [_camera startVideoRecordingAtPath:call.arguments[@"filePath"] result:result]; + } else if ([@"stopVideoRecording" isEqualToString:call.method]) { + [_camera stopVideoRecordingWithResult:result]; + } else { + result(FlutterMethodNotImplemented); + } } - } } @end diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 40943f6b9a81..2199123cd5b7 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -215,7 +215,7 @@ class CameraController extends ValueNotifier { enableAudio: enableAudio); Size previewSize = await CameraPlatform.instance - .onResolutionChanged(_cameraId) + .onCameraResolutionChanged(_cameraId) .take(1) .map((event) => Size( event.previewWidth.toDouble(), 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 c74b94e590b0..379f8fab49e0 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 @@ -33,8 +33,32 @@ abstract class CameraEvent { int get hashCode => cameraId.hashCode; } +/// An event fired when the camera has finished initializing. +class CameraInitializedEvent extends CameraResolutionChangedEvent { + /// Build a CameraInitialized event triggered from the camera represented by + /// `cameraId`. + /// + /// The `captureWidth` represents the width of the resulting image in pixels. + /// The `captureHeight` represents the height of the resulting image in pixels. + /// The `previewWidth` represents the width of the generated preview in pixels. + /// The `previewHeight` represents the height of the generated preview in pixels. + CameraInitializedEvent( + int cameraId, + int captureWidth, + int captureHeight, + int previewWidth, + int previewHeight, + ) : super( + cameraId, + captureWidth, + captureHeight, + previewWidth, + previewHeight, + ); +} + /// An event fired when the resolution preset of the camera has changed. -class ResolutionChangedEvent extends CameraEvent { +class CameraResolutionChangedEvent extends CameraEvent { /// The capture width in pixels. final int captureWidth; @@ -47,12 +71,14 @@ class ResolutionChangedEvent extends CameraEvent { /// The height of the preview in pixels. final int previewHeight; - /// Build a ResolutionChanged event triggered from the camera represented by - /// `cameraId`. + /// Build a CameraResolutionChanged event triggered from the camera + /// represented by `cameraId`. /// - /// The `captureSize` represents the size of the resulting image in pixels. - /// The `previewSize` represents the size of the generated preview in pixels. - ResolutionChangedEvent( + /// The `captureWidth` represents the width of the resulting image in pixels. + /// The `captureHeight` represents the height of the resulting image in pixels. + /// The `previewWidth` represents the width of the generated preview in pixels. + /// The `previewHeight` represents the height of the generated preview in pixels. + CameraResolutionChangedEvent( int cameraId, this.captureWidth, this.captureHeight, @@ -60,7 +86,7 @@ class ResolutionChangedEvent extends CameraEvent { this.previewHeight, ) : super(cameraId); - ResolutionChangedEvent.fromJson(Map json) + CameraResolutionChangedEvent.fromJson(Map json) : captureWidth = json['captureWidth'], captureHeight = json['captureHeight'], previewWidth = json['previewWidth'], @@ -78,7 +104,7 @@ class ResolutionChangedEvent extends CameraEvent { @override bool operator ==(Object other) => identical(this, other) || - other is ResolutionChangedEvent && + other is CameraResolutionChangedEvent && super == (other) && runtimeType == other.runtimeType && captureWidth == other.captureWidth && 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 e5768e0ca4f1..6faf82470016 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 @@ -5,11 +5,11 @@ import 'dart:async'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; +import 'package:cross_file/cross_file.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; -import 'package:rxdart/rxdart.dart'; +import 'package:stream_transform/stream_transform.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); @@ -20,8 +20,6 @@ class MethodChannelCamera extends CameraPlatform { final StreamController _cameraEventStreamController = StreamController.broadcast(); - final Map _cameraResolutionChangedEventStreams = {}; - Stream _events(int cameraId) => _cameraEventStreamController.stream .where((event) => event.cameraId == cameraId); @@ -44,16 +42,15 @@ class MethodChannelCamera extends CameraPlatform { } @override - Future initializeCamera( + Future createCamera( CameraDescription cameraDescription, ResolutionPreset resolutionPreset, { bool enableAudio, }) async { - int _cameraId; try { final Map reply = await _channel.invokeMapMethod( - 'initialize', + 'create', { 'cameraName': cameraDescription.name, 'resolutionPreset': resolutionPreset != null @@ -62,21 +59,20 @@ class MethodChannelCamera extends CameraPlatform { 'enableAudio': enableAudio, }, ); - _cameraId = reply['cameraId']; + return reply['cameraId']; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } - if (!_channels.containsKey(_cameraId)) { - final channel = - MethodChannel('flutter.io/cameraPlugin/camera$_cameraId'); + } + + @override + Future initializeCamera(int cameraId) async { + if (!_channels.containsKey(cameraId)) { + final channel = MethodChannel('flutter.io/cameraPlugin/camera$cameraId'); channel.setMethodCallHandler( - (MethodCall call) => handleMethodCall(call, _cameraId)); - _channels[_cameraId] = channel; - _cameraResolutionChangedEventStreams[_cameraId] = _events(_cameraId) - .whereType() - .shareReplay(maxSize: 1); + (MethodCall call) => handleMethodCall(call, cameraId)); + _channels[cameraId] = channel; } - return _cameraId; } @override @@ -89,8 +85,13 @@ class MethodChannelCamera extends CameraPlatform { } @override - Stream onResolutionChanged(int cameraId) { - return _cameraResolutionChangedEventStreams[cameraId]; + Stream onCameraInitialized(int cameraId) { + return _events(cameraId).whereType(); + } + + @override + Stream onCameraResolutionChanged(int cameraId) { + return _events(cameraId).whereType(); } @override @@ -114,7 +115,7 @@ class MethodChannelCamera extends CameraPlatform { @override Future prepareForVideoRecording() => - _channel.invokeMethod('prepareForVideoRecording'); + _channel.invokeMethod('prepareForVideoRecording'); @override Future startVideoRecording(int cameraId) async { @@ -134,18 +135,17 @@ class MethodChannelCamera extends CameraPlatform { } @override - Future pauseVideoRecording(int cameraId) => - _channel.invokeMethod( - 'pauseVideoRecording', - {'cameraId': cameraId}, - ); + Future pauseVideoRecording(int cameraId) => _channel.invokeMethod( + 'pauseVideoRecording', + {'cameraId': cameraId}, + ); @override Future resumeVideoRecording(int cameraId) => - _channel.invokeMethod( - 'resumeVideoRecording', - {'cameraId': cameraId}, - ); + _channel.invokeMethod( + 'resumeVideoRecording', + {'cameraId': cameraId}, + ); @override Widget buildView(int cameraId) { @@ -189,7 +189,7 @@ class MethodChannelCamera extends CameraPlatform { Future handleMethodCall(MethodCall call, int cameraId) async { switch (call.method) { case 'resolution_changed': - _cameraEventStreamController.add(ResolutionChangedEvent( + _cameraEventStreamController.add(CameraResolutionChangedEvent( cameraId, call.arguments['captureWidth'], call.arguments['captureHeight'], 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 b8a73612b0cf..46c226154e7b 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 @@ -43,17 +43,22 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('availableCameras() is not implemented.'); } - /// Initializes the camera on the device and returns its cameraId. - Future initializeCamera( + /// Creates an uninitialized camera instance and returns the cameraId. + Future createCamera( CameraDescription cameraDescription, ResolutionPreset resolutionPreset, { bool enableAudio, }) { + throw UnimplementedError('createCamera() is not implemented.'); + } + + /// Initializes the camera on the device. + Future initializeCamera(int cameraId) { throw UnimplementedError('initializeCamera() is not implemented.'); } /// The camera's resolution has changed - Stream onResolutionChanged(int cameraId) { + Stream onCameraResolutionChanged(int cameraId) { throw UnimplementedError('onResolutionChanged() is not implemented.'); } diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 9b2065e792e8..e72d2367df9c 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -10,8 +10,8 @@ dependencies: sdk: flutter meta: ^1.0.5 plugin_platform_interface: ^1.0.1 - file_selector_platform_interface: ^1.0.0 - rxdart: ^0.24.1 + cross_file: ^0.1.0 + stream_transform: ^1.2.0 dev_dependencies: flutter_test: diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index 11003bf60cec..1690c4fa4567 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_test/flutter_test.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; @@ -52,7 +52,7 @@ void main() { // Act & Assert expect( - () => cameraPlatform.onResolutionChanged(1), + () => cameraPlatform.onCameraResolutionChanged(1), throwsUnimplementedError, ); }); diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index e3cdd84fc432..b72bfe940fd4 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -97,15 +97,15 @@ void main() { test('Should receive resolution changes', () async { // Act - final Stream resolutionStream = - camera.onResolutionChanged(cameraId); + final Stream resolutionStream = + camera.onCameraResolutionChanged(cameraId); final streamQueue = StreamQueue(resolutionStream); // Emit test events final fhdEvent = - ResolutionChangedEvent(cameraId, 1920, 1080, 1280, 720); + CameraResolutionChangedEvent(cameraId, 1920, 1080, 1280, 720); final uhdEvent = - ResolutionChangedEvent(cameraId, 3840, 2160, 1280, 720); + CameraResolutionChangedEvent(cameraId, 3840, 2160, 1280, 720); await camera.handleMethodCall( MethodCall('camera#resolutionChanged', fhdEvent.toJson()), cameraId); diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec b/packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec new file mode 100644 index 000000000000..5ca30416bac0 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec @@ -0,0 +1,18 @@ +# +# NOTE: This podspec is NOT to be published. It is only used as a local source! +# + +Pod::Spec.new do |s| + s.name = 'Flutter' + s.version = '1.0.0' + s.summary = 'High-performance, high-fidelity mobile apps.' + s.description = <<-DESC +Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. + DESC + s.homepage = 'https://flutter.io' + s.license = { :type => 'MIT' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } + s.ios.deployment_target = '8.0' + s.vendored_frameworks = 'Flutter.framework' +end diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj index f6a2d6ec291a..75392aeb82e5 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,11 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -28,8 +24,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -40,14 +34,12 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -63,8 +55,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -83,9 +73,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -230,7 +218,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 74BF216DF17B0C7F983459BD /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -270,9 +258,12 @@ files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", + "${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -285,9 +276,12 @@ files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../Flutter/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -331,7 +325,6 @@ /* Begin XCBuildConfiguration section */ 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -388,7 +381,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + From b7b33e5a3ced888d2adc16d1634501171e3f6b25 Mon Sep 17 00:00:00 2001 From: daniel Date: Thu, 26 Nov 2020 14:34:24 +0100 Subject: [PATCH 43/89] More tests and some feedback --- packages/camera/camera/example/lib/main.dart | 4 +- packages/camera/camera/lib/camera.dart | 60 +------ packages/camera/camera/lib/camera_image.dart | 3 +- .../test}/camera_image_test.dart | 2 +- packages/camera/camera/test/camera_test.dart | 168 +++++++++++++++++- .../camera/camera/test/camera_value_test.dart | 2 +- .../lib/camera_platform_interface.dart | 3 +- .../method_channel/method_channel_camera.dart | 22 ++- .../lib/src/types/camera_exception.dart | 2 +- .../lib/src/types/camera_image.dart | 148 --------------- .../lib/src/types/types.dart | 3 +- .../test/method_channel_camera_test.dart | 12 +- 12 files changed, 195 insertions(+), 234 deletions(-) rename packages/camera/{camera_platform_interface/test/types => camera/test}/camera_image_test.dart (97%) delete mode 100644 packages/camera/camera_platform_interface/lib/src/types/camera_image.dart diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 529bb9b8f97b..aa99724528af 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -130,8 +130,8 @@ class _CameraExampleHomeState extends State ), ); } else { - return Container( - // aspectRatio: controller.value.aspectRatio, + return AspectRatio( + aspectRatio: controller.value.aspectRatio, child: controller.buildView(), ); } diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 40943f6b9a81..48d688788246 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -29,39 +29,6 @@ Future> availableCameras() async { return CameraPlatform.instance.availableCameras(); } -/// This is thrown when the plugin reports an error. -class CameraException implements Exception { - /// Creates a new camera exception with the given error code and description. - CameraException(this.code, this.description); - - /// Error code. - // TODO(bparrishMines): Document possible error codes. - // https://github.com/flutter/flutter/issues/69298 - String code; - - /// Textual description of the error. - String description; - - @override - String toString() => '$runtimeType($code, $description)'; -} - -/// A widget showing a live camera preview. -class CameraPreview extends StatelessWidget { - /// Creates a preview widget for the given camera controller. - const CameraPreview(this.controller); - - /// The controller for the camera that the preview is shown for. - final CameraController controller; - - @override - Widget build(BuildContext context) { - return controller.value.isInitialized - ? Texture(textureId: controller._cameraId) - : Container(); - } -} - /// The state of a [CameraController]. class CameraValue { /// Creates a new camera controller state. @@ -150,7 +117,6 @@ class CameraValue { @override String toString() { return '$runtimeType(' - 'isRecordingVideo: $isRecordingVideo, ' 'isRecordingVideo: $isRecordingVideo, ' 'isInitialized: $isInitialized, ' 'errorDescription: $errorDescription, ' @@ -205,7 +171,10 @@ class CameraController extends ValueNotifier { /// Throws a [CameraException] if the initialization fails. Future initialize() async { if (_isDisposed) { - return Future.value(); + throw CameraException( + 'Disposed CameraController', + 'initialize was called on a disposed CameraController', + ); } try { _creatingCompleter = Completer(); @@ -249,27 +218,6 @@ class CameraController extends ValueNotifier { await CameraPlatform.instance.prepareForVideoRecording(); } - /// Listen to events from the native plugins. - /// - /// A "cameraClosing" event is sent when the camera is closed automatically by the system (for example when the app go to background). The plugin will try to reopen the camera automatically but any ongoing recording will end. - void _listener(dynamic event) { - //TODO: Replace - debugPrint("event is $event"); - final Map map = event; - if (_isDisposed) { - return; - } - - switch (map['eventType']) { - case 'error': - value = value.copyWith(errorDescription: event['errorDescription']); - break; - case 'cameraClosing': - value = value.copyWith(isRecordingVideo: false); - break; - } - } - /// Captures an image and saves it to [path]. /// /// A path can for example be obtained using diff --git a/packages/camera/camera/lib/camera_image.dart b/packages/camera/camera/lib/camera_image.dart index cebc14873f52..0b84b19be6e8 100644 --- a/packages/camera/camera/lib/camera_image.dart +++ b/packages/camera/camera/lib/camera_image.dart @@ -113,7 +113,8 @@ ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) { /// Although not all image formats are planar on iOS, we treat 1-dimensional /// images as single planar images. class CameraImage { - CameraImage._fromPlatformData(Map data) + /// CameraImage Constructor + CameraImage.fromPlatformData(Map data) : format = ImageFormat._fromPlatformData(data['format']), height = data['height'], width = data['width'], diff --git a/packages/camera/camera_platform_interface/test/types/camera_image_test.dart b/packages/camera/camera/test/camera_image_test.dart similarity index 97% rename from packages/camera/camera_platform_interface/test/types/camera_image_test.dart rename to packages/camera/camera/test/camera_image_test.dart index 61ed75225cdf..c8f808f2c1a1 100644 --- a/packages/camera/camera_platform_interface/test/types/camera_image_test.dart +++ b/packages/camera/camera/test/camera_image_test.dart @@ -4,7 +4,7 @@ import 'dart:typed_data'; -import 'package:camera_platform_interface/src/types/camera_image.dart'; +import 'package:camera/camera.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index d2521617a4ed..7b3db21f4b34 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1,11 +1,44 @@ // Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; +import 'dart:ui'; + import 'package:camera/camera.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; +import 'package:pedantic/pedantic.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:rxdart/rxdart.dart'; + +get mockAvailableCameras => [ + CameraDescription( + name: 'camBack', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + CameraDescription( + name: 'camFront', + lensDirection: CameraLensDirection.front, + sensorOrientation: 180), + ]; + +get mockInitializeCamera => 13; + +get mockOnResolutionChangedEvent => + ResolutionChangedEvent(13, 100, 100, 75, 75); + +get mockOnCameraClosingEvent => null; + +get mockOnCameraErrorEvent => CameraErrorEvent(13, 'closing'); + +get mockTakePicture => null; + +get mockVideoRecordingXFile => null; void main() { + WidgetsFlutterBinding.ensureInitialized(); group('camera', () { test('debugCheckIsDisposed should not throw assertion error when disposed', () { @@ -33,10 +66,143 @@ void main() { throwsAssertionError, ); }); + + test('availableCameras() has camera', () async { + CameraPlatform.instance = MockCameraPlatform(); + + var camList = await availableCameras(); + + expect(camList, equals(mockAvailableCameras)); + }); + }); + + group('$CameraController', () { + setUpAll(() { + CameraPlatform.instance = MockCameraPlatform(); + }); + + test('Can be initialized', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect(cameraController.value.aspectRatio, 1); + expect(cameraController.value.previewSize, Size(75, 75)); + expect(cameraController.value.isInitialized, isTrue); + }); + + test('can be disposed', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect(cameraController.value.aspectRatio, 1); + expect(cameraController.value.previewSize, Size(75, 75)); + expect(cameraController.value.isInitialized, isTrue); + + await cameraController.dispose(); + + verify(CameraPlatform.instance.dispose(13)).called(1); + }); + + test('initialize() returns when disposed', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect(cameraController.value.aspectRatio, 1); + expect(cameraController.value.previewSize, Size(75, 75)); + expect(cameraController.value.isInitialized, isTrue); + + await cameraController.dispose(); + + verify(CameraPlatform.instance.dispose(13)).called(1); + expect(cameraController.value.isInitialized, isFalse); + + await cameraController.initialize(); + expect(cameraController.value.isInitialized, isTrue); + + + }); }); } -class MockCameraPlatform extends Mock implements CameraPlatform {} +class MockCameraPlatform extends Mock + with MockPlatformInterfaceMixin + implements CameraPlatform { + + @override + Future> availableCameras() => + Future.value(mockAvailableCameras); + + @override + Future initializeCamera( + CameraDescription cameraDescription, + ResolutionPreset resolutionPreset, { + bool enableAudio, + }) => + Future.value(mockInitializeCamera); + + @override + Stream onResolutionChanged(int cameraId) { + return Stream.value(mockOnResolutionChangedEvent); + } + + @override + Stream onCameraClosing(int cameraId) { + return Stream.value(mockOnCameraClosingEvent); + } + + @override + Stream onCameraError(int cameraId) { + return Stream.value(mockOnCameraErrorEvent); + } + + @override + Future takePicture(int cameraId) => Future.value(mockTakePicture); + + // @override + // Future prepareForVideoRecording() { + // + // } + + @override + Future startVideoRecording(int cameraId) => + Future.value(mockVideoRecordingXFile); + +// @override +// Future stopVideoRecording(int cameraId) { +// +// } + +// @override +// Future pauseVideoRecording(int cameraId) { +// throw UnimplementedError('pauseVideoRecording() is not implemented.'); +// } + +// @override +// Future resumeVideoRecording(int cameraId) { +// throw UnimplementedError('resumeVideoRecording() is not implemented.'); +// } + +// @override +// Widget buildView(int cameraId) { +// throw UnimplementedError('buildView() has not been implemented.'); +// } + +} class MockCameraDescription extends CameraDescription { @override diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart index 34fb4ad13e88..4a70f059cb5e 100644 --- a/packages/camera/camera/test/camera_value_test.dart +++ b/packages/camera/camera/test/camera_value_test.dart @@ -92,7 +92,7 @@ void main() { ); expect(cameraValue.toString(), - 'CameraValue(isRecordingVideo: false, isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false)'); + 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false)'); }); }); } diff --git a/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart b/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart index 4956105d305d..84d7be965703 100644 --- a/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart +++ b/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart @@ -5,5 +5,6 @@ export 'src/events/camera_event.dart'; export 'src/platform_interface/camera_platform.dart'; export 'src/types/types.dart'; + /// Expose XFile -export 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; \ No newline at end of file +export 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; 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 e5768e0ca4f1..907916505530 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 @@ -67,8 +67,7 @@ class MethodChannelCamera extends CameraPlatform { throw CameraException(e.code, e.message); } if (!_channels.containsKey(_cameraId)) { - final channel = - MethodChannel('flutter.io/cameraPlugin/camera$_cameraId'); + final channel = MethodChannel('flutter.io/cameraPlugin/camera$_cameraId'); channel.setMethodCallHandler( (MethodCall call) => handleMethodCall(call, _cameraId)); _channels[_cameraId] = channel; @@ -114,7 +113,7 @@ class MethodChannelCamera extends CameraPlatform { @override Future prepareForVideoRecording() => - _channel.invokeMethod('prepareForVideoRecording'); + _channel.invokeMethod('prepareForVideoRecording'); @override Future startVideoRecording(int cameraId) async { @@ -134,18 +133,17 @@ class MethodChannelCamera extends CameraPlatform { } @override - Future pauseVideoRecording(int cameraId) => - _channel.invokeMethod( - 'pauseVideoRecording', - {'cameraId': cameraId}, - ); + Future pauseVideoRecording(int cameraId) => _channel.invokeMethod( + 'pauseVideoRecording', + {'cameraId': cameraId}, + ); @override Future resumeVideoRecording(int cameraId) => - _channel.invokeMethod( - 'resumeVideoRecording', - {'cameraId': cameraId}, - ); + _channel.invokeMethod( + 'resumeVideoRecording', + {'cameraId': cameraId}, + ); @override Widget buildView(int cameraId) { diff --git a/packages/camera/camera_platform_interface/lib/src/types/camera_exception.dart b/packages/camera/camera_platform_interface/lib/src/types/camera_exception.dart index 00fb36eedb9f..5734f3e11612 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/camera_exception.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/camera_exception.dart @@ -17,4 +17,4 @@ class CameraException implements Exception { @override String toString() => '$runtimeType($code, $description)'; -} \ No newline at end of file +} diff --git a/packages/camera/camera_platform_interface/lib/src/types/camera_image.dart b/packages/camera/camera_platform_interface/lib/src/types/camera_image.dart deleted file mode 100644 index 5662f130d8a9..000000000000 --- a/packages/camera/camera_platform_interface/lib/src/types/camera_image.dart +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:typed_data'; - -import 'package:flutter/foundation.dart'; - -/// A single color plane of image data. -/// -/// The number and meaning of the planes in an image are determined by the -/// format of the Image. -class Plane { - Plane._fromPlatformData(Map data) - : bytes = data['bytes'], - bytesPerPixel = data['bytesPerPixel'], - bytesPerRow = data['bytesPerRow'], - height = data['height'], - width = data['width']; - - /// Bytes representing this plane. - final Uint8List bytes; - - /// The distance between adjacent pixel samples on Android, in bytes. - /// - /// Will be `null` on iOS. - final int bytesPerPixel; - - /// The row stride for this color plane, in bytes. - final int bytesPerRow; - - /// Height of the pixel buffer on iOS. - /// - /// Will be `null` on Android - final int height; - - /// Width of the pixel buffer on iOS. - /// - /// Will be `null` on Android. - final int width; -} - -// TODO:(bmparr) Turn [ImageFormatGroup] to a class with int values. -/// Group of image formats that are comparable across Android and iOS platforms. -enum ImageFormatGroup { - /// The image format does not fit into any specific group. - unknown, - - /// Multi-plane YUV 420 format. - /// - /// This format is a generic YCbCr format, capable of describing any 4:2:0 - /// chroma-subsampled planar or semiplanar buffer (but not fully interleaved), - /// with 8 bits per color sample. - /// - /// On Android, this is `android.graphics.ImageFormat.YUV_420_888`. See - /// https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888 - /// - /// On iOS, this is `kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange`. See - /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_420ypcbcr8biplanarvideorange?language=objc - yuv420, - - /// 32-bit BGRA. - /// - /// On iOS, this is `kCVPixelFormatType_32BGRA`. See - /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_32bgra?language=objc - bgra8888, -} - -/// Describes how pixels are represented in an image. -class ImageFormat { - ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw); - - /// Describes the format group the raw image format falls into. - final ImageFormatGroup group; - - /// Raw version of the format from the Android or iOS platform. - /// - /// On Android, this is an `int` from class `android.graphics.ImageFormat`. See - /// https://developer.android.com/reference/android/graphics/ImageFormat - /// - /// On iOS, this is a `FourCharCode` constant from Pixel Format Identifiers. - /// See https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers?language=objc - final dynamic raw; -} - -ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) { - if (defaultTargetPlatform == TargetPlatform.android) { - // android.graphics.ImageFormat.YUV_420_888 - if (rawFormat == 35) { - return ImageFormatGroup.yuv420; - } - } - - if (defaultTargetPlatform == TargetPlatform.iOS) { - switch (rawFormat) { - // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange - case 875704438: - return ImageFormatGroup.yuv420; - // kCVPixelFormatType_32BGRA - case 1111970369: - return ImageFormatGroup.bgra8888; - } - } - - return ImageFormatGroup.unknown; -} - -/// A single complete image buffer from the platform camera. -/// -/// This class allows for direct application access to the pixel data of an -/// Image through one or more [Uint8List]. Each buffer is encapsulated in a -/// [Plane] that describes the layout of the pixel data in that plane. The -/// [CameraImage] is not directly usable as a UI resource. -/// -/// Although not all image formats are planar on iOS, we treat 1-dimensional -/// images as single planar images. -class CameraImage { - /// Creates a new [CameraImage] from the data received from the platform. - CameraImage.fromPlatformData(Map data) - : format = ImageFormat._fromPlatformData(data['format']), - height = data['height'], - width = data['width'], - planes = List.unmodifiable(data['planes'] - .map((dynamic planeData) => Plane._fromPlatformData(planeData))); - - /// Format of the image provided. - /// - /// Determines the number of planes needed to represent the image, and - /// the general layout of the pixel data in each [Uint8List]. - final ImageFormat format; - - /// Height of the image in pixels. - /// - /// For formats where some color channels are subsampled, this is the height - /// of the largest-resolution plane. - final int height; - - /// Width of the image in pixels. - /// - /// For formats where some color channels are subsampled, this is the width - /// of the largest-resolution plane. - final int width; - - /// The pixels planes for this image. - /// - /// The number of planes is determined by the format of the image. - final List planes; -} diff --git a/packages/camera/camera_platform_interface/lib/src/types/types.dart b/packages/camera/camera_platform_interface/lib/src/types/types.dart index b3eab45ff7ee..71e7a97ef49a 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/types.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/types.dart @@ -3,6 +3,5 @@ // found in the LICENSE file. export 'camera_description.dart'; -export 'camera_image.dart'; export 'resolution_preset.dart'; -export 'camera_exception.dart'; \ No newline at end of file +export 'camera_exception.dart'; diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 59d9bfb62e2e..79237cdc1bb4 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -107,17 +107,13 @@ void main() { final uhdEvent = ResolutionChangedEvent(cameraId, 3840, 2160, 1280, 720); await camera.handleMethodCall( - MethodCall('resolution_changed', fhdEvent.toJson()), - cameraId); + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('resolution_changed', uhdEvent.toJson()), - cameraId); + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('resolution_changed', fhdEvent.toJson()), - cameraId); + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('resolution_changed', uhdEvent.toJson()), - cameraId); + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); // Assert expect(await streamQueue.next, fhdEvent); From 8bd9718f29a7d5ace19c1ad78aafd335949444d3 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 26 Nov 2020 14:45:00 +0100 Subject: [PATCH 44/89] Finish splitting up initialize for iOS --- .../camera/example/ios/Flutter/.last_build_id | 2 +- .../camera/camera/ios/Classes/CameraPlugin.m | 46 +++++++------- packages/camera/camera/lib/camera.dart | 34 ++++++---- .../lib/camera_platform_interface.dart | 2 +- .../lib/src/events/camera_event.dart | 63 ++++++++----------- .../method_channel/method_channel_camera.dart | 22 ++++++- .../platform_interface/camera_platform.dart | 7 ++- 7 files changed, 96 insertions(+), 80 deletions(-) diff --git a/packages/camera/camera/example/ios/Flutter/.last_build_id b/packages/camera/camera/example/ios/Flutter/.last_build_id index 003e79479ba4..1d0b0dc32be3 100644 --- a/packages/camera/camera/example/ios/Flutter/.last_build_id +++ b/packages/camera/camera/example/ios/Flutter/.last_build_id @@ -1 +1 @@ -ae2fda886f38c43aa68f58016de359e7 \ No newline at end of file +5490cb309144ac61a67edda5c46bb18b \ No newline at end of file diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index a722b65b1063..7adb4558ed74 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -801,28 +801,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re @"cameraId" : @(textureId), }); } - } else if ([@"initialize" isEqualToString:call.method]) { - NSNumber* cameraIdArg = call.arguments[@"cameraId"]; - int64_t cameraId = [cameraIdArg longLongValue]; - - __weak CameraPlugin *weakSelf = self; - _camera.onFrameAvailable = ^{ - [weakSelf.registry textureFrameAvailable:cameraId]; - }; - - FlutterMethodChannel *methodChannel = [FlutterMethodChannel - methodChannelWithName:[NSString - stringWithFormat:@"flutter.io/cameraPlugin/camera%lld", - cameraId] - binaryMessenger:_messenger]; - _camera.methodChannel = methodChannel; - [methodChannel invokeMethod:@"initialized" arguments:@{ - @"previewWidth" : @(_camera.previewSize.width), - @"previewHeight" : @(_camera.previewSize.height) - }]; - [_camera start]; - result(nil); - } else if ([@"startImageStream" isEqualToString:call.method]) { + } else if ([@"startImageStream" isEqualToString:call.method]) { [_camera startImageStreamWithMessenger:_messenger]; result(nil); } else if ([@"stopImageStream" isEqualToString:call.method]) { @@ -836,15 +815,32 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re result(nil); } else { NSDictionary *argsMap = call.arguments; - NSUInteger textureId = ((NSNumber *)argsMap[@"textureId"]).unsignedIntegerValue; - if ([@"takePicture" isEqualToString:call.method]) { + NSUInteger cameraId = ((NSNumber *)argsMap[@"cameraId"]).unsignedIntegerValue; + if ([@"initialize" isEqualToString:call.method]) { + __weak CameraPlugin *weakSelf = self; + _camera.onFrameAvailable = ^{ + [weakSelf.registry textureFrameAvailable:cameraId]; + }; + FlutterMethodChannel *methodChannel = [FlutterMethodChannel + methodChannelWithName:[NSString + stringWithFormat:@"flutter.io/cameraPlugin/camera%lld", + (long long) cameraId] + binaryMessenger:_messenger]; + _camera.methodChannel = methodChannel; + [methodChannel invokeMethod:@"initialized" arguments:@{ + @"previewWidth" : @(_camera.previewSize.width), + @"previewHeight" : @(_camera.previewSize.height) + }]; + [_camera start]; + result(nil); + } else if ([@"takePicture" isEqualToString:call.method]) { if (@available(iOS 10.0, *)) { [_camera captureToFile:call.arguments[@"path"] result:result]; } else { result(FlutterMethodNotImplemented); } } else if ([@"dispose" isEqualToString:call.method]) { - [_registry unregisterTexture:textureId]; + [_registry unregisterTexture:cameraId]; [_camera close]; _dispatchQueue = nil; result(nil); diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 2199123cd5b7..1925340f5a4a 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -9,7 +9,7 @@ import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; - +import 'package:pedantic/pedantic.dart'; export 'package:camera_platform_interface/camera_platform_interface.dart'; part 'camera_image.dart'; @@ -209,23 +209,31 @@ class CameraController extends ValueNotifier { } try { _creatingCompleter = Completer(); + Completer _resolutionCompleter = Completer(); + + _cameraId = await CameraPlatform.instance.createCamera( + description, + resolutionPreset, + enableAudio: enableAudio, + ); - _cameraId = await CameraPlatform.instance.initializeCamera( - description, resolutionPreset, - enableAudio: enableAudio); + unawaited( + CameraPlatform.instance + .onCameraInitialized(_cameraId) + .map((event) => Size( + event.previewWidth, + event.previewHeight, + )) + + .first + .then((previewSize) => _resolutionCompleter.complete(previewSize)), + ); - Size previewSize = await CameraPlatform.instance - .onCameraResolutionChanged(_cameraId) - .take(1) - .map((event) => Size( - event.previewWidth.toDouble(), - event.previewHeight.toDouble(), - )) - .first; + await CameraPlatform.instance.initializeCamera(_cameraId); value = value.copyWith( isInitialized: true, - previewSize: previewSize, + previewSize: await _resolutionCompleter.future, ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); diff --git a/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart b/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart index 4956105d305d..ad29f3b36f8a 100644 --- a/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart +++ b/packages/camera/camera_platform_interface/lib/camera_platform_interface.dart @@ -6,4 +6,4 @@ export 'src/events/camera_event.dart'; export 'src/platform_interface/camera_platform.dart'; export 'src/types/types.dart'; /// Expose XFile -export 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; \ No newline at end of file +export 'package:cross_file/cross_file.dart'; \ No newline at end of file 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 379f8fab49e0..37b4034a1068 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 @@ -34,71 +34,66 @@ abstract class CameraEvent { } /// An event fired when the camera has finished initializing. -class CameraInitializedEvent extends CameraResolutionChangedEvent { +class CameraInitializedEvent extends CameraEvent { + /// The width of the preview in pixels. + final double previewWidth; + + /// The height of the preview in pixels. + final double previewHeight; + /// Build a CameraInitialized event triggered from the camera represented by /// `cameraId`. /// - /// The `captureWidth` represents the width of the resulting image in pixels. - /// The `captureHeight` represents the height of the resulting image in pixels. /// The `previewWidth` represents the width of the generated preview in pixels. /// The `previewHeight` represents the height of the generated preview in pixels. CameraInitializedEvent( int cameraId, - int captureWidth, - int captureHeight, - int previewWidth, - int previewHeight, - ) : super( - cameraId, - captureWidth, - captureHeight, - previewWidth, - previewHeight, - ); + this.previewWidth, + this.previewHeight, + ) : super(cameraId); + + @override + bool operator ==(Object other) => + identical(this, other) || + super == other && + other is CameraInitializedEvent && + runtimeType == other.runtimeType && + previewWidth == other.previewWidth && + previewHeight == other.previewHeight; + + @override + int get hashCode => + super.hashCode ^ previewWidth.hashCode ^ previewHeight.hashCode; } /// An event fired when the resolution preset of the camera has changed. class CameraResolutionChangedEvent extends CameraEvent { /// The capture width in pixels. - final int captureWidth; + final double captureWidth; /// The capture height in pixels. - final int captureHeight; - - /// The width of the preview in pixels. - final int previewWidth; - - /// The height of the preview in pixels. - final int previewHeight; + final double captureHeight; /// Build a CameraResolutionChanged event triggered from the camera /// represented by `cameraId`. /// /// The `captureWidth` represents the width of the resulting image in pixels. /// The `captureHeight` represents the height of the resulting image in pixels. - /// The `previewWidth` represents the width of the generated preview in pixels. - /// The `previewHeight` represents the height of the generated preview in pixels. CameraResolutionChangedEvent( int cameraId, this.captureWidth, this.captureHeight, - this.previewWidth, - this.previewHeight, ) : super(cameraId); CameraResolutionChangedEvent.fromJson(Map json) : captureWidth = json['captureWidth'], captureHeight = json['captureHeight'], - previewWidth = json['previewWidth'], - previewHeight = json['previewHeight'], super(json['cameraId']); Map toJson() => { 'cameraId': cameraId, 'captureWidth': captureWidth, 'captureHeight': captureHeight, - 'previewWidth': previewWidth, - 'previewHeight': previewHeight, }; @override @@ -108,17 +103,13 @@ class CameraResolutionChangedEvent extends CameraEvent { super == (other) && runtimeType == other.runtimeType && captureWidth == other.captureWidth && - captureHeight == other.captureHeight && - previewWidth == other.previewWidth && - previewHeight == other.previewHeight; + captureHeight == other.captureHeight; @override int get hashCode => super.hashCode ^ captureWidth.hashCode ^ - captureHeight.hashCode ^ - previewWidth.hashCode ^ - previewHeight.hashCode; + captureHeight.hashCode; } /// An event fired when the camera is going to close. 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 6faf82470016..65ca042e012c 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 @@ -66,13 +66,24 @@ class MethodChannelCamera extends CameraPlatform { } @override - Future initializeCamera(int cameraId) async { + Future initializeCamera(int cameraId) { if (!_channels.containsKey(cameraId)) { final channel = MethodChannel('flutter.io/cameraPlugin/camera$cameraId'); channel.setMethodCallHandler( (MethodCall call) => handleMethodCall(call, cameraId)); _channels[cameraId] = channel; } + Completer _completer = Completer(); + onCameraInitialized(cameraId).first.then((value) { + _completer.complete(); + }); + _channel.invokeMapMethod( + 'initialize', + { + 'cameraId': cameraId, + }, + ); + return _completer.future; } @override @@ -188,13 +199,18 @@ class MethodChannelCamera extends CameraPlatform { @visibleForTesting Future handleMethodCall(MethodCall call, int cameraId) async { switch (call.method) { + case 'initialized': + _cameraEventStreamController.sink.add(CameraInitializedEvent( + cameraId, + call.arguments['previewWidth'], + call.arguments['previewHeight'], + )); + break; case 'resolution_changed': _cameraEventStreamController.add(CameraResolutionChangedEvent( cameraId, call.arguments['captureWidth'], call.arguments['captureHeight'], - call.arguments['previewWidth'], - call.arguments['previewHeight'], )); break; case 'camera_closing': 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 46c226154e7b..5d4c4ce8c513 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 @@ -5,7 +5,7 @@ import 'dart:async'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; -import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; +import 'package:cross_file/cross_file.dart'; import 'package:flutter/widgets.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; @@ -57,6 +57,11 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('initializeCamera() is not implemented.'); } + /// The camera has been initialized + Stream onCameraInitialized(int cameraId) { + throw UnimplementedError('onCameraInitialized() is not implemented.'); + } + /// The camera's resolution has changed Stream onCameraResolutionChanged(int cameraId) { throw UnimplementedError('onResolutionChanged() is not implemented.'); From c7ffb497fdc016436bcf98abf4bebf236ea8427d Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 26 Nov 2020 15:29:37 +0100 Subject: [PATCH 45/89] Update unit tests --- .../lib/src/events/camera_event.dart | 15 +- .../method_channel/method_channel_camera.dart | 13 +- .../test/camera_platform_interface_test.dart | 30 +++- .../test/method_channel_camera_test.dart | 128 +++++++++++++----- 4 files changed, 141 insertions(+), 45 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 37b4034a1068..3746ae481584 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 @@ -52,6 +52,17 @@ class CameraInitializedEvent extends CameraEvent { this.previewHeight, ) : super(cameraId); + CameraInitializedEvent.fromJson(Map json) + : previewWidth = json['previewWidth'], + previewHeight = json['previewHeight'], + super(json['cameraId']); + + Map toJson() => { + 'cameraId': cameraId, + 'previewWidth': previewWidth, + 'previewHeight': previewHeight, + }; + @override bool operator ==(Object other) => identical(this, other) || @@ -107,9 +118,7 @@ class CameraResolutionChangedEvent extends CameraEvent { @override int get hashCode => - super.hashCode ^ - captureWidth.hashCode ^ - captureHeight.hashCode; + super.hashCode ^ captureWidth.hashCode ^ captureHeight.hashCode; } /// An event fired when the camera is going to close. 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 65ca042e012c..ab27918d8af2 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 @@ -17,11 +17,12 @@ const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); class MethodChannelCamera extends CameraPlatform { final Map _channels = {}; - final StreamController _cameraEventStreamController = + @visibleForTesting + final StreamController cameraEventStreamController = StreamController.broadcast(); Stream _events(int cameraId) => - _cameraEventStreamController.stream + cameraEventStreamController.stream .where((event) => event.cameraId == cameraId); @override @@ -200,26 +201,26 @@ class MethodChannelCamera extends CameraPlatform { Future handleMethodCall(MethodCall call, int cameraId) async { switch (call.method) { case 'initialized': - _cameraEventStreamController.sink.add(CameraInitializedEvent( + cameraEventStreamController.add(CameraInitializedEvent( cameraId, call.arguments['previewWidth'], call.arguments['previewHeight'], )); break; case 'resolution_changed': - _cameraEventStreamController.add(CameraResolutionChangedEvent( + cameraEventStreamController.add(CameraResolutionChangedEvent( cameraId, call.arguments['captureWidth'], call.arguments['captureHeight'], )); break; case 'camera_closing': - _cameraEventStreamController.add(CameraClosingEvent( + cameraEventStreamController.add(CameraClosingEvent( cameraId, )); break; case 'error': - _cameraEventStreamController.add(CameraErrorEvent( + cameraEventStreamController.add(CameraErrorEvent( cameraId, call.arguments['description'], )); diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index 1690c4fa4567..2bc35bf4055c 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -44,6 +44,19 @@ void main() { ); }); + test( + 'Default implementation of onCameraInitialized() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.onCameraInitialized(1), + throwsUnimplementedError, + ); + }); + test( 'Default implementation of onResolutionChanged() should throw unimplemented error', () { @@ -96,14 +109,27 @@ void main() { }); test( - 'Default implementation of initialize() should throw unimplemented error', + 'Default implementation of createCamera() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.createCamera(null, null), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of initializeCamera() should throw unimplemented error', () { // Arrange final cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( - () => cameraPlatform.initializeCamera(null, null), + () => cameraPlatform.initializeCamera(null), throwsUnimplementedError, ); }); diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index b72bfe940fd4..4f9e65e67f82 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -7,7 +7,6 @@ import 'dart:async'; import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; -import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -18,27 +17,26 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$MethodChannelCamera', () { - group('Initialization & Disposal Tests', () { - test('Should receive a camera id when initialized', () async { + group('Creation, Initialization & Disposal Tests', () { + test('Should send creation data and receive back a camera id', () async { // Arrange MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'cameraId': 1} + 'create': {'cameraId': 1} }); final camera = MethodChannelCamera(); // Act - final cameraId = await camera.initializeCamera( + final cameraId = await camera.createCamera( CameraDescription(name: 'Test'), ResolutionPreset.high, ); // Assert - expect(cameraId, 1); expect(cameraMockChannel.log, [ isMethodCall( - 'initialize', + 'create', arguments: { 'cameraName': 'Test', 'resolutionPreset': 'high', @@ -46,6 +44,40 @@ void main() { }, ), ]); + expect(cameraId, 1); + }); + + test('Should send initialization data', () async { + // Arrange + MethodChannelMock cameraMockChannel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'create': {'cameraId': 1}, + 'initialize': null + }); + final camera = MethodChannelCamera(); + final cameraId = await camera.createCamera( + CameraDescription(name: 'Test'), + ResolutionPreset.high, + ); + + // Act + Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController + .add(CameraInitializedEvent(cameraId, 1920, 1080)); + await initializeFuture; + + // Assert + expect(cameraId, 1); + expect(cameraMockChannel.log, [ + anything, + isMethodCall( + 'initialize', + arguments: { + 'cameraId': 1, + }, + ), + ]); }); test('Should send a disposal call on dispose', () async { @@ -53,15 +85,20 @@ void main() { MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'cameraId': 1}, + 'create': {'cameraId': 1}, + 'initialize': null, 'dispose': {'cameraId': 1} }); final camera = MethodChannelCamera(); - final cameraId = await camera.initializeCamera( + final cameraId = await camera.createCamera( CameraDescription(name: 'Test'), ResolutionPreset.high, ); + Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController + .add(CameraInitializedEvent(cameraId, 1920, 1080)); + await initializeFuture; // Act await camera.dispose(cameraId); @@ -69,7 +106,8 @@ void main() { // Assert expect(cameraId, 1); expect(cameraMockChannel.log, [ - isNotNull, + anything, + anything, isMethodCall( 'dispose', arguments: {'cameraId': 1}, @@ -85,14 +123,37 @@ void main() { MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'cameraId': 1}, + 'create': {'cameraId': 1}, + 'initialize': null }, ); camera = MethodChannelCamera(); - cameraId = await camera.initializeCamera( + cameraId = await camera.createCamera( CameraDescription(name: 'Test'), ResolutionPreset.high, ); + Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController + .add(CameraInitializedEvent(cameraId, 1920, 1080)); + await initializeFuture; + }); + + test('Should receive initialized event', () async { + // Act + final Stream eventStream = + camera.onCameraInitialized(cameraId); + final streamQueue = StreamQueue(eventStream); + + // Emit test events + final event = CameraInitializedEvent(cameraId, 3840, 2160); + await camera.handleMethodCall( + MethodCall('initialized', event.toJson()), cameraId); + + // Assert + expect(await streamQueue.next, event); + + // Clean up + await streamQueue.cancel(); }); test('Should receive resolution changes', () async { @@ -102,30 +163,24 @@ void main() { final streamQueue = StreamQueue(resolutionStream); // Emit test events - final fhdEvent = - CameraResolutionChangedEvent(cameraId, 1920, 1080, 1280, 720); - final uhdEvent = - CameraResolutionChangedEvent(cameraId, 3840, 2160, 1280, 720); + final fhdEvent = CameraResolutionChangedEvent(cameraId, 1920, 1080); + final uhdEvent = CameraResolutionChangedEvent(cameraId, 3840, 2160); await camera.handleMethodCall( - MethodCall('camera#resolutionChanged', fhdEvent.toJson()), - cameraId); + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#resolutionChanged', uhdEvent.toJson()), - cameraId); + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#resolutionChanged', fhdEvent.toJson()), - cameraId); + MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#resolutionChanged', uhdEvent.toJson()), - cameraId); + MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); // Assert expect(await streamQueue.next, fhdEvent); expect(await streamQueue.next, uhdEvent); expect(await streamQueue.next, fhdEvent); expect(await streamQueue.next, uhdEvent); - // - // // Clean up + + // Clean up await streamQueue.cancel(); }); @@ -138,11 +193,11 @@ void main() { // Emit test events final event = CameraClosingEvent(cameraId); await camera.handleMethodCall( - MethodCall('camera#closing', event.toJson()), cameraId); + MethodCall('camera_closing', event.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#closing', event.toJson()), cameraId); + MethodCall('camera_closing', event.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#closing', event.toJson()), cameraId); + MethodCall('camera_closing', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); @@ -161,11 +216,11 @@ void main() { // Emit test events final event = CameraErrorEvent(cameraId, 'Error Description'); await camera.handleMethodCall( - MethodCall('camera#error', event.toJson()), cameraId); + MethodCall('error', event.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#error', event.toJson()), cameraId); + MethodCall('error', event.toJson()), cameraId); await camera.handleMethodCall( - MethodCall('camera#error', event.toJson()), cameraId); + MethodCall('error', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); @@ -184,14 +239,19 @@ void main() { MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'initialize': {'cameraId': 1}, + 'create': {'cameraId': 1}, + 'initialize': null }, ); camera = MethodChannelCamera(); - cameraId = await camera.initializeCamera( + cameraId = await camera.createCamera( CameraDescription(name: 'Test'), ResolutionPreset.high, ); + Future initializeFuture = camera.initializeCamera(cameraId); + camera.cameraEventStreamController + .add(CameraInitializedEvent(cameraId, 1920, 1080)); + await initializeFuture; }); test('Should fetch CameraDescription instances for available cameras', From b8f286ca0464474b06bc3262087cfb7356553592 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 26 Nov 2020 15:45:38 +0100 Subject: [PATCH 46/89] Remove unused listener in plugin --- packages/camera/camera/lib/camera.dart | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 1925340f5a4a..34e5d3311552 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -257,27 +257,6 @@ class CameraController extends ValueNotifier { await CameraPlatform.instance.prepareForVideoRecording(); } - /// Listen to events from the native plugins. - /// - /// A "cameraClosing" event is sent when the camera is closed automatically by the system (for example when the app go to background). The plugin will try to reopen the camera automatically but any ongoing recording will end. - void _listener(dynamic event) { - //TODO: Replace - debugPrint("event is $event"); - final Map map = event; - if (_isDisposed) { - return; - } - - switch (map['eventType']) { - case 'error': - value = value.copyWith(errorDescription: event['errorDescription']); - break; - case 'cameraClosing': - value = value.copyWith(isRecordingVideo: false); - break; - } - } - /// Captures an image and saves it to [path]. /// /// A path can for example be obtained using From 349b26ff28f730abacdb56f24e2a8d1f82ed6044 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 26 Nov 2020 17:46:01 +0100 Subject: [PATCH 47/89] Fix takePicture method on iOS --- .../camera/camera/ios/Classes/CameraPlugin.m | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 7adb4558ed74..ebfe7626f728 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -7,6 +7,7 @@ #import #import #import +#import static FlutterError *getFlutterError(NSError *error) { return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] @@ -44,20 +45,39 @@ @implementation FLTSavePhotoDelegate { FLTSavePhotoDelegate *selfReference; } -- initWithPath:(NSString *)path - result:(FlutterResult)result - motionManager:(CMMotionManager *)motionManager -cameraPosition:(AVCaptureDevicePosition)cameraPosition { +- initForResult:(FlutterResult)result + motionManager:(CMMotionManager *)motionManager + cameraPosition:(AVCaptureDevicePosition)cameraPosition { self = [super init]; NSAssert(self, @"super init cannot be nil"); - _path = path; _result = result; _motionManager = motionManager; _cameraPosition = cameraPosition; selfReference = self; + _path = [self getTemporaryFilePathWithExtension:@"jpg" prefix:@"CAP"]; return self; } +- (NSString*)getTemporaryFilePathWithExtension:(NSString*) extension prefix:(NSString*) prefix +{ + NSString *docDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; + NSString *fileDir = [[docDir stringByAppendingPathComponent:@"camera"] stringByAppendingPathComponent:@"pictures"]; + NSString *fileName = [prefix stringByAppendingString:[[NSUUID UUID] UUIDString]]; + NSString *file = [[fileDir stringByAppendingPathComponent:fileName] stringByAppendingPathExtension:extension]; + + NSFileManager *fm = [NSFileManager defaultManager]; + if(![fm fileExistsAtPath:fileDir]) { + NSError *error; + [[NSFileManager defaultManager] createDirectoryAtPath:fileDir withIntermediateDirectories:true attributes:nil error:&error]; + if (error) { + _result(getFlutterError(error)); + return nil; + } + } + + return file; +} + - (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer @@ -81,7 +101,7 @@ - (void)captureOutput:(AVCapturePhotoOutput *)output _result([FlutterError errorWithCode:@"IOError" message:@"Unable to write file" details:nil]); return; } - _result(nil); + _result(_path); } - (UIImageOrientation)getImageRotation { @@ -256,17 +276,17 @@ - (void)stop { [_captureSession stopRunning]; } -- (void)captureToFile:(NSString *)path result:(FlutterResult)result API_AVAILABLE(ios(10)) { +- (void)captureToFile:(FlutterResult)result API_AVAILABLE(ios(10)) { AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings]; if (_resolutionPreset == max) { [settings setHighResolutionPhotoEnabled:YES]; } [_capturePhotoOutput capturePhotoWithSettings:settings - delegate:[[FLTSavePhotoDelegate alloc] initWithPath:path - result:result + delegate:[[FLTSavePhotoDelegate alloc] initForResult:result motionManager:_motionManager - cameraPosition:_captureDevice.position]]; + cameraPosition:_captureDevice.position + ]]; } - (void)setCaptureSessionPreset:(ResolutionPreset)resolutionPreset { @@ -835,7 +855,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re result(nil); } else if ([@"takePicture" isEqualToString:call.method]) { if (@available(iOS 10.0, *)) { - [_camera captureToFile:call.arguments[@"path"] result:result]; + [_camera captureToFile:result]; } else { result(FlutterMethodNotImplemented); } From efcfcb4e568419ede7c7c4082010705ed4799f0c Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 27 Nov 2020 12:52:05 +0100 Subject: [PATCH 48/89] Fix video recording on iOS. Updated platform interface. --- packages/camera/camera/example/lib/main.dart | 32 ++++----- .../camera/camera/ios/Classes/CameraPlugin.m | 70 +++++++++++-------- packages/camera/camera/lib/camera.dart | 24 +++---- .../method_channel/method_channel_camera.dart | 10 +-- .../platform_interface/camera_platform.dart | 15 ++-- 5 files changed, 76 insertions(+), 75 deletions(-) diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 529bb9b8f97b..d0b117df1407 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -9,7 +9,6 @@ import 'dart:io'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; class CameraExampleHome extends StatefulWidget { @@ -319,19 +318,19 @@ class _CameraExampleHomeState extends State } void onVideoRecordButtonPressed() { - startVideoRecording().then((XFile file) { + startVideoRecording().then((_) { if (mounted) setState(() {}); - if (file != null) { - showInSnackBar('Saving video to ${file.path}'); - videoFile = file; - } }); } void onStopButtonPressed() { - stopVideoRecording().then((_) { + stopVideoRecording().then((file) { if (mounted) setState(() {}); - showInSnackBar('Video recorded to: ${videoFile.path}'); + if (file != null) { + showInSnackBar('Video recorded to ${file.path}'); + videoFile = file; + _startVideoPlayer(); + } }); } @@ -349,39 +348,36 @@ class _CameraExampleHomeState extends State }); } - Future startVideoRecording() async { + Future startVideoRecording() async { if (!controller.value.isInitialized) { showInSnackBar('Error: select a camera first.'); - return null; + return; } if (controller.value.isRecordingVideo) { // A recording is already started, do nothing. - return null; + return; } try { - XFile file = await controller.startVideoRecording(); - return file; + await controller.startVideoRecording(); } on CameraException catch (e) { _showCameraException(e); - return null; + return; } } - Future stopVideoRecording() async { + Future stopVideoRecording() async { if (!controller.value.isRecordingVideo) { return null; } try { - await controller.stopVideoRecording(); + return controller.stopVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return null; } - - await _startVideoPlayer(); } Future pauseVideoRecording() async { diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index ebfe7626f728..862e2b613f24 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -45,39 +45,20 @@ @implementation FLTSavePhotoDelegate { FLTSavePhotoDelegate *selfReference; } -- initForResult:(FlutterResult)result +- initWithPath:(NSString *)path + result:(FlutterResult)result motionManager:(CMMotionManager *)motionManager cameraPosition:(AVCaptureDevicePosition)cameraPosition { self = [super init]; NSAssert(self, @"super init cannot be nil"); - _result = result; + _path = path; _motionManager = motionManager; _cameraPosition = cameraPosition; selfReference = self; - _path = [self getTemporaryFilePathWithExtension:@"jpg" prefix:@"CAP"]; + _result = result; return self; } -- (NSString*)getTemporaryFilePathWithExtension:(NSString*) extension prefix:(NSString*) prefix -{ - NSString *docDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; - NSString *fileDir = [[docDir stringByAppendingPathComponent:@"camera"] stringByAppendingPathComponent:@"pictures"]; - NSString *fileName = [prefix stringByAppendingString:[[NSUUID UUID] UUIDString]]; - NSString *file = [[fileDir stringByAppendingPathComponent:fileName] stringByAppendingPathExtension:extension]; - - NSFileManager *fm = [NSFileManager defaultManager]; - if(![fm fileExistsAtPath:fileDir]) { - NSError *error; - [[NSFileManager defaultManager] createDirectoryAtPath:fileDir withIntermediateDirectories:true attributes:nil error:&error]; - if (error) { - _result(getFlutterError(error)); - return nil; - } - } - - return file; -} - - (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer @@ -193,6 +174,7 @@ @interface FLTCam : NSObject _videoWriter.status == AVAssetWriterStatusCompleted) { - result(nil); + result(self->_videoRecordingPath); + self->_videoRecordingPath = nil; } else { [self->_methodChannel invokeMethod:@"error" arguments:@"AVAssetWriter could not finish writing!"]; } @@ -868,7 +882,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [_camera setUpCaptureSessionForAudio]; result(nil); } else if ([@"startVideoRecording" isEqualToString:call.method]) { - [_camera startVideoRecordingAtPath:call.arguments[@"filePath"] result:result]; + [_camera startVideoRecordingWithResult:result]; } else if ([@"stopVideoRecording" isEqualToString:call.method]) { [_camera stopVideoRecordingWithResult:result]; } else { diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 34e5d3311552..178f4818bb65 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -352,17 +352,11 @@ class CameraController extends ValueNotifier { _imageStreamSubscription = null; } - /// Start a video recording and save the file to [path]. - /// - /// A path can for example be obtained using - /// [path_provider](https://pub.dartlang.org/packages/path_provider). - /// - /// The file is written on the flight as the video is being recorded. - /// If a file already exists at the provided path an error will be thrown. - /// The file can be read as soon as [stopVideoRecording] returns. + /// Start a video recording. /// + /// The file can be read as soon as [stopVideoRecording] returns it. /// Throws a [CameraException] if the capture fails. - Future startVideoRecording() async { + Future startVideoRecording() async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -383,16 +377,17 @@ class CameraController extends ValueNotifier { } try { - XFile file = await CameraPlatform.instance.startVideoRecording(_cameraId); + await CameraPlatform.instance.startVideoRecording(_cameraId); value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); - return file; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } - /// Stop recording. - Future stopVideoRecording() async { + /// Stops the video recording and returns the file where it was saved. + /// + /// Throws a [CameraException] if the capture failed. + Future stopVideoRecording() async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -406,8 +401,9 @@ class CameraController extends ValueNotifier { ); } try { - await CameraPlatform.instance.stopVideoRecording(_cameraId); + XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); value = value.copyWith(isRecordingVideo: false); + return file; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } 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 ab27918d8af2..80d76e9f0e31 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 @@ -130,20 +130,20 @@ class MethodChannelCamera extends CameraPlatform { _channel.invokeMethod('prepareForVideoRecording'); @override - Future startVideoRecording(int cameraId) async { - String path = await _channel.invokeMethod( + Future startVideoRecording(int cameraId) async { + await _channel.invokeMethod( 'startVideoRecording', {'cameraId': cameraId}, ); - return XFile(path); } @override - Future stopVideoRecording(int cameraId) async { - await _channel.invokeMethod( + Future stopVideoRecording(int cameraId) async { + String path = await _channel.invokeMethod( 'stopVideoRecording', {'cameraId': cameraId}, ); + return XFile(path); } @override 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 5d4c4ce8c513..a73db364b727 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,20 +87,15 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('prepareForVideoRecording() is not implemented.'); } - /// Starts a video recording and returns the file where it will be saved. + /// Starts a video recording. /// - /// The file is written on the flight as the video is being recorded. - /// The file can be read as soon as [stopVideoRecording] returns. - Future startVideoRecording(int cameraId) { + /// The file can be read as soon as [stopVideoRecording] returns it. + Future startVideoRecording(int cameraId) { throw UnimplementedError('startVideoRecording() is not implemented.'); } - /// Stops the video recording. - /// - /// When the [stopVideoRecording] method completes successfully the recorded - /// video can be accessed through the file returned by the - /// [startVideoRecording] method. - Future stopVideoRecording(int cameraId) { + /// Stops the video recording and returns the file where it was saved. + Future stopVideoRecording(int cameraId) { throw UnimplementedError('stopVideoRecording() is not implemented.'); } From 8b22d1009ca397c8a417f7b512b31cc5596a62f2 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 27 Nov 2020 12:55:06 +0100 Subject: [PATCH 49/89] Update unit tests --- .../test/method_channel_camera_test.dart | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 4f9e65e67f82..009be3846088 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -319,15 +319,15 @@ void main() { ]); }); - test('Should start recording a video and return an XFile instance', - () async { + test('Should start recording a video', () async { // Arrange MethodChannelMock channel = MethodChannelMock( - channelName: 'plugins.flutter.io/camera', - methods: {'startVideoRecording': '/test/path.mkv'}); + channelName: 'plugins.flutter.io/camera', + methods: {'startVideoRecording': null}, + ); // Act - XFile file = await camera.startVideoRecording(cameraId); + await camera.startVideoRecording(cameraId); // Assert expect(channel.log, [ @@ -335,18 +335,17 @@ void main() { 'cameraId': cameraId, }), ]); - expect(file.path, '/test/path.mkv'); }); - test('Should stop a video recording', () async { + test('Should stop a video recording and return the file', () async { // Arrange MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', - methods: {'stopVideoRecording': null}, + methods: {'stopVideoRecording': '/test/path.mp4'}, ); // Act - await camera.stopVideoRecording(cameraId); + XFile file = await camera.stopVideoRecording(cameraId); // Assert expect(channel.log, [ @@ -354,6 +353,7 @@ void main() { 'cameraId': cameraId, }), ]); + expect(file.path, '/test/path.mp4'); }); test('Should pause a video recording', () async { From f11850a3598e59eafdca926d31354cd30c07c8d9 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 27 Nov 2020 13:04:45 +0100 Subject: [PATCH 50/89] Update error handling of video methods in iOS code. Make iOS code more consistent. --- .../camera/camera/ios/Classes/CameraPlugin.m | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 862e2b613f24..ecaad73a57de 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -580,7 +580,7 @@ - (void)startVideoRecordingWithResult:(FlutterResult)result { return; } if (![self setupWriterForPath:_videoRecordingPath]) { - [_methodChannel invokeMethod:@"error" arguments:@"Setup Writer Failed"]; + result([FlutterError errorWithCode:@"IOError" message:@"Setup Writer Failed" details:nil]); return; } _isRecording = YES; @@ -591,7 +591,7 @@ - (void)startVideoRecordingWithResult:(FlutterResult)result { _audioIsDisconnected = NO; result(nil); } else { - [_methodChannel invokeMethod:@"error" arguments:@"Video is already recording"]; + result([FlutterError errorWithCode:@"Error" message:@"Video is already recording" details:nil]); } } @@ -604,7 +604,7 @@ - (void)stopVideoRecordingWithResult:(FlutterResult)result { result(self->_videoRecordingPath); self->_videoRecordingPath = nil; } else { - [self->_methodChannel invokeMethod:@"error" arguments:@"AVAssetWriter could not finish writing!"]; + result([FlutterError errorWithCode:@"IOError" message:@"AVAssetWriter could not finish writing!" details:nil]); } }]; } @@ -617,14 +617,16 @@ - (void)stopVideoRecordingWithResult:(FlutterResult)result { } } -- (void)pauseVideoRecording { +- (void)pauseVideoRecordingWithResult:(FlutterResult)result { _isRecordingPaused = YES; _videoIsDisconnected = YES; _audioIsDisconnected = YES; + result(nil); } -- (void)resumeVideoRecording { +- (void)resumeVideoRecordingWithResult:(FlutterResult)result { _isRecordingPaused = NO; + result(nil); } - (void)startImageStreamWithMessenger:(NSObject *)messenger { @@ -841,12 +843,6 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re } else if ([@"stopImageStream" isEqualToString:call.method]) { [_camera stopImageStream]; result(nil); - } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { - [_camera pauseVideoRecording]; - result(nil); - } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { - [_camera resumeVideoRecording]; - result(nil); } else { NSDictionary *argsMap = call.arguments; NSUInteger cameraId = ((NSNumber *)argsMap[@"cameraId"]).unsignedIntegerValue; @@ -885,6 +881,10 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [_camera startVideoRecordingWithResult:result]; } else if ([@"stopVideoRecording" isEqualToString:call.method]) { [_camera stopVideoRecordingWithResult:result]; + } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { + [_camera pauseVideoRecordingWithResult:result]; + } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { + [_camera resumeVideoRecordingWithResult:result]; } else { result(FlutterMethodNotImplemented); } From 7f14ff7da042004653c0835104323b6e83b39668 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 15:04:47 +0100 Subject: [PATCH 51/89] Split initialize method on Android --- .../io/flutter/plugins/camera/Camera.java | 12 ++++----- .../flutter/plugins/camera/DartMessenger.java | 4 +-- .../plugins/camera/MethodCallHandlerImpl.java | 25 +++++++++++++++++-- 3 files changed, 30 insertions(+), 11 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 480ee7c35ab4..cb3f62b2c7cd 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 @@ -132,7 +132,7 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { } @SuppressLint("MissingPermission") - public void open(@NonNull final Result result) throws CameraAccessException { + public void open() throws CameraAccessException { pictureImageReader = ImageReader.newInstance( captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2); @@ -151,14 +151,14 @@ public void onOpened(@NonNull CameraDevice device) { try { startPreview(); } catch (CameraAccessException e) { - result.error("CameraAccess", e.getMessage(), null); + dartMessenger.sendCameraErrorEvent(e.getMessage()); close(); return; } - Map reply = new HashMap<>(); - reply.put("cameraId", flutterTexture.id()); - result.success(reply); - dartMessenger.sendResolutionChangedEvent(previewSize.getWidth(), previewSize.getHeight(), null, null); + + dartMessenger.sendInitializedEvent( + previewSize.getWidth(), + previewSize.getHeight()); } @Override 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 9f31fb77875c..bbe4f374ecf6 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 @@ -25,12 +25,10 @@ enum EventType { channel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); } - void sendResolutionChangedEvent(Integer previewWidth, Integer previewHeight, Integer captureWidth, Integer captureHeight) { + void sendInitializedEvent(Integer previewWidth, Integer previewHeight) { this.send(EventType.RESOLUTION_CHANGED, new HashMap() {{ if (previewWidth != null) put("previewWidth", previewWidth); if (previewHeight != null) put("previewHeight", previewHeight); - if (captureWidth != null) put("captureWidth", captureWidth); - if (captureHeight != null) put("captureHeight", captureHeight); }}); } 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 b6fcf89ac362..c2125a475dca 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 @@ -11,6 +11,8 @@ import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry; import io.flutter.view.TextureRegistry; +import java.util.HashMap; +import java.util.Map; final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { private final Activity activity; @@ -49,11 +51,12 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) handleException(e, result); } break; - case "initialize": + case "create": { if (camera != null) { camera.close(); } + cameraPermissions.requestPermissions( activity, permissionsRegistry, @@ -71,6 +74,22 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) }); break; } + case "initialize": { + if (camera != null) { + try { + camera.open(); + result.success(null); + } catch (Exception e) { + handleException(e, result); + } + } else { + result.error( + "cameraNotFound", + "Camera not found. Please call the 'create' method before calling 'initialize'.", + null); + } + break; + } case "takePicture": { camera.takePicture(result); @@ -156,7 +175,9 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce resolutionPreset, enableAudio); - camera.open(result); + Map reply = new HashMap<>(); + reply.put("cameraId", flutterSurfaceTexture.id()); + result.success(reply); } // We move catching CameraAccessException out of onMethodCall because it causes a crash From a6f8985409bad08c5a680342e2a9d42034985d88 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 15:29:45 +0100 Subject: [PATCH 52/89] Updated startVideoRecording documentation --- packages/camera/camera/lib/camera.dart | 2 +- .../lib/src/platform_interface/camera_platform.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 7179052f64e4..ddce736d8ce4 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -323,7 +323,7 @@ class CameraController extends ValueNotifier { /// Start a video recording. /// - /// The file can be read as soon as [stopVideoRecording] returns it. + /// The video is returned as a [XFile] after calling [stopVideoRecording]. /// Throws a [CameraException] if the capture fails. Future startVideoRecording() async { if (!value.isInitialized || _isDisposed) { 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 a73db364b727..3d2b6fb0acf2 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 @@ -89,7 +89,7 @@ abstract class CameraPlatform extends PlatformInterface { /// Starts a video recording. /// - /// The file can be read as soon as [stopVideoRecording] returns it. + /// The video is returned as a [XFile] after calling [stopVideoRecording]. Future startVideoRecording(int cameraId) { throw UnimplementedError('startVideoRecording() is not implemented.'); } From 3e53a35df92653b530c745d4de4d6f412dd9e62e Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 15:59:52 +0100 Subject: [PATCH 53/89] Make sure file is returned by stopVideoRecording --- .../main/java/io/flutter/plugins/camera/Camera.java | 13 ++++++++----- 1 file changed, 8 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 cb3f62b2c7cd..2983279a64df 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 @@ -60,6 +60,7 @@ public class Camera { private CaptureRequest.Builder captureRequestBuilder; private MediaRecorder mediaRecorder; private boolean recordingVideo; + private File videoRecordingFile; private CamcorderProfile recordingProfile; private int currentOrientation = ORIENTATION_UNKNOWN; private Context applicationContext; @@ -336,21 +337,22 @@ public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession public void startVideoRecording(Result result) { final File outputDir = applicationContext.getCacheDir(); - final File file; try { - file = File.createTempFile("REC", ".mp4", outputDir); + videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir); } catch (IOException e) { result.error("cannotCreateFile", e.getMessage(), null); return; } try { - prepareMediaRecorder(file.getAbsolutePath()); + prepareMediaRecorder(videoRecordingFile.getAbsolutePath()); recordingVideo = true; createCaptureSession( CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); - result.success(file.getAbsolutePath()); + result.success(null); } catch (CameraAccessException | IOException e) { + recordingVideo = false; + videoRecordingFile = null; result.error("videoRecordingFailed", e.getMessage(), null); } } @@ -366,7 +368,8 @@ public void stopVideoRecording(@NonNull final Result result) { mediaRecorder.stop(); mediaRecorder.reset(); startPreview(); - result.success(null); + result.success(videoRecordingFile.getAbsolutePath()); + videoRecordingFile = null; } catch (CameraAccessException | IllegalStateException e) { result.error("videoRecordingFailed", e.getMessage(), null); } From 418abc025e162565de424066ce1b8dea6098259d Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 27 Nov 2020 16:01:31 +0100 Subject: [PATCH 54/89] Change cast --- packages/camera/camera/ios/Classes/CameraPlugin.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index ecaad73a57de..72acbb1369b7 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -853,8 +853,8 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re }; FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:[NSString - stringWithFormat:@"flutter.io/cameraPlugin/camera%lld", - (long long) cameraId] + stringWithFormat:@"flutter.io/cameraPlugin/camera%lu", + (unsigned long) cameraId] binaryMessenger:_messenger]; _camera.methodChannel = methodChannel; [methodChannel invokeMethod:@"initialized" arguments:@{ From d36789bbd52f5608e1e1e0682d1936ea9dc665a4 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 16:04:46 +0100 Subject: [PATCH 55/89] Use correct event-type after initializing --- .../java/io/flutter/plugins/camera/Camera.java | 2 +- .../io/flutter/plugins/camera/DartMessenger.java | 7 +++---- .../plugins/camera/DartMessengerTest.java | 16 ++++++++++++++++ 3 files changed, 20 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 2983279a64df..0f47ad37785b 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 @@ -157,7 +157,7 @@ public void onOpened(@NonNull CameraDevice device) { return; } - dartMessenger.sendInitializedEvent( + dartMessenger.sendCameraInitializedEvent( previewSize.getWidth(), previewSize.getHeight()); } 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 bbe4f374ecf6..1d5166222ea2 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,19 +14,18 @@ class DartMessenger { @Nullable private MethodChannel channel; - enum EventType { ERROR, CAMERA_CLOSING, - RESOLUTION_CHANGED, + CAMERA_INITIALIZED, } DartMessenger(BinaryMessenger messenger, long cameraId) { channel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); } - void sendInitializedEvent(Integer previewWidth, Integer previewHeight) { - this.send(EventType.RESOLUTION_CHANGED, new HashMap() {{ + void sendCameraInitializedEvent(Integer previewWidth, Integer previewHeight) { + this.send(EventType.CAMERA_INITIALIZED, new HashMap() {{ if (previewWidth != null) put("previewWidth", previewWidth); if (previewHeight != null) put("previewHeight", previewHeight); }}); diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index c5ea83aa058a..f352ef6341c4 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -7,6 +7,7 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; +import io.flutter.plugins.camera.DartMessenger.EventType; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -79,6 +80,21 @@ public void send_includesErrorDescriptions() { assertEquals("error description", event.get("errorDescription")); } + @Test + public void sendCameraInitializedEvent() { + initializeEventSink(); + + dartMessenger.sendCameraInitializedEvent(0, 0); + + List sentMessages = fakeBinaryMessenger.getMessages(); + assertEquals(1, sentMessages.size()); + Map event = decodeSentMessage(sentMessages.get(0)); + assertEquals( + EventType.CAMERA_INITIALIZED.toString().toLowerCase(), event.get("eventType")); + assertNull(event.get("previewWidth")); + assertNull(event.get("previewHeight")); + } + @Test public void sendCameraClosingEvent() { initializeEventSink(); From 2a5cc5ae3cb9de1a360f75445ddc60d4557969f5 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 19:22:43 +0100 Subject: [PATCH 56/89] Fix DartMessenger unit-tests --- .../flutter/plugins/camera/DartMessenger.java | 8 +- .../plugins/camera/DartMessengerTest.java | 73 +++++-------------- packages/camera/camera/lib/camera.dart | 11 +-- .../method_channel/method_channel_camera.dart | 14 ++-- 4 files changed, 37 insertions(+), 69 deletions(-) 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 1d5166222ea2..89939a3e2fb0 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 @@ -17,7 +17,7 @@ class DartMessenger { enum EventType { ERROR, CAMERA_CLOSING, - CAMERA_INITIALIZED, + INITIALIZED, } DartMessenger(BinaryMessenger messenger, long cameraId) { @@ -25,9 +25,9 @@ enum EventType { } void sendCameraInitializedEvent(Integer previewWidth, Integer previewHeight) { - this.send(EventType.CAMERA_INITIALIZED, new HashMap() {{ - if (previewWidth != null) put("previewWidth", previewWidth); - if (previewHeight != null) put("previewHeight", previewHeight); + this.send(EventType.INITIALIZED, new HashMap() {{ + if (previewWidth != null) put("previewWidth", previewWidth.doubleValue()); + if (previewHeight != null) put("previewHeight", previewHeight.doubleValue()); }}); } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index f352ef6341c4..b203e9bb1888 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -2,8 +2,8 @@ import static junit.framework.TestCase.assertNull; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import androidx.annotation.NonNull; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; @@ -11,33 +11,26 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import java.util.Map; import org.junit.Before; import org.junit.Test; public class DartMessengerTest { /** A {@link BinaryMessenger} implementation that does nothing but save its messages. */ private static class FakeBinaryMessenger implements BinaryMessenger { - private BinaryMessageHandler handler; private final List sentMessages = new ArrayList<>(); @Override - public void send(String channel, ByteBuffer message) { + public void send(@NonNull String channel, ByteBuffer message) { sentMessages.add(message); } @Override - public void send(String channel, ByteBuffer message, BinaryReply callback) { + public void send(@NonNull String channel, ByteBuffer message, BinaryReply callback) { send(channel, message); } @Override - public void setMessageHandler(String channel, BinaryMessageHandler handler) { - this.handler = handler; - } - - BinaryMessageHandler getMessageHandler() { - return handler; + public void setMessageHandler(@NonNull String channel, BinaryMessageHandler handler) { } List getMessages() { @@ -55,70 +48,42 @@ public void setUp() { } @Test - public void setsStreamHandler() { - assertNotNull(fakeBinaryMessenger.getMessageHandler()); - } - - @Test - public void send_handlesNullEventSinks() { - dartMessenger.send(DartMessenger.EventType.ERROR, "error description"); - - List sentMessages = fakeBinaryMessenger.getMessages(); - assertEquals(0, sentMessages.size()); - } - - @Test - public void send_includesErrorDescriptions() { - initializeEventSink(); - - dartMessenger.send(DartMessenger.EventType.ERROR, "error description"); + public void sendCameraErrorEvent_includesErrorDescriptions() { + dartMessenger.sendCameraErrorEvent("error description"); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); - Map event = decodeSentMessage(sentMessages.get(0)); - assertEquals(DartMessenger.EventType.ERROR.toString().toLowerCase(), event.get("eventType")); - assertEquals("error description", event.get("errorDescription")); + MethodCall call = decodeSentMessage(sentMessages.get(0)); + assertEquals(DartMessenger.EventType.ERROR.toString().toLowerCase(), call.method); + assertEquals("error description", call.argument("description")); } @Test - public void sendCameraInitializedEvent() { - initializeEventSink(); - + public void sendCameraInitializedEvent_includesPreviewSize() { dartMessenger.sendCameraInitializedEvent(0, 0); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); - Map event = decodeSentMessage(sentMessages.get(0)); - assertEquals( - EventType.CAMERA_INITIALIZED.toString().toLowerCase(), event.get("eventType")); - assertNull(event.get("previewWidth")); - assertNull(event.get("previewHeight")); + MethodCall call = decodeSentMessage(sentMessages.get(0)); + assertEquals(EventType.INITIALIZED.toString().toLowerCase(), call.method); + assertEquals(0, (double)call.argument("previewWidth"), 0); + assertEquals(0, (double)call.argument("previewHeight"), 0); } @Test public void sendCameraClosingEvent() { - initializeEventSink(); - dartMessenger.sendCameraClosingEvent(); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); - Map event = decodeSentMessage(sentMessages.get(0)); + MethodCall call = decodeSentMessage(sentMessages.get(0)); assertEquals( - DartMessenger.EventType.CAMERA_CLOSING.toString().toLowerCase(), event.get("eventType")); - assertNull(event.get("errorDescription")); + DartMessenger.EventType.CAMERA_CLOSING.toString().toLowerCase(), call.method); + assertNull(call.argument("description")); } - private Map decodeSentMessage(ByteBuffer sentMessage) { + private MethodCall decodeSentMessage(ByteBuffer sentMessage) { sentMessage.position(0); - //noinspection unchecked - return (Map) StandardMethodCodec.INSTANCE.decodeEnvelope(sentMessage); - } - - private void initializeEventSink() { - MethodCall call = new MethodCall("listen", null); - ByteBuffer encodedCall = StandardMethodCodec.INSTANCE.encodeMethodCall(call); - encodedCall.position(0); - fakeBinaryMessenger.getMessageHandler().onMessage(encodedCall, reply -> {}); + return StandardMethodCodec.INSTANCE.decodeMethodCall(sentMessage); } } diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index ddce736d8ce4..fa11c1108061 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -189,11 +189,12 @@ class CameraController extends ValueNotifier { unawaited( CameraPlatform.instance .onCameraInitialized(_cameraId) - .map((event) => Size( - event.previewWidth, - event.previewHeight, - )) - + .map((event) { + return Size( + event.previewWidth, + event.previewHeight, + ); + }) .first .then((previewSize) => _resolutionCompleter.complete(previewSize)), ); 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 d1708088a5b4..c66994283876 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 @@ -21,9 +21,11 @@ class MethodChannelCamera extends CameraPlatform { final StreamController cameraEventStreamController = StreamController.broadcast(); - Stream _events(int cameraId) => - cameraEventStreamController.stream - .where((event) => event.cameraId == cameraId); + Stream _events(int cameraId) => cameraEventStreamController + .stream + .tap((event) => print( + 'Event received, EVENT TYPE: ${event.runtimeType}, STRING: ${event.toString()}')) + .where((event) => event.cameraId == cameraId); @override Future> availableCameras() async { @@ -76,18 +78,18 @@ class MethodChannelCamera extends CameraPlatform { } Completer _completer = Completer(); - + onCameraInitialized(cameraId).first.then((value) { _completer.complete(); }); - + _channel.invokeMapMethod( 'initialize', { 'cameraId': cameraId, }, ); - + return _completer.future; } From 1e7d19af954cdc65fd83dd0467cb7bced66c6bff Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 19:26:23 +0100 Subject: [PATCH 57/89] Fix formatting --- .../io/flutter/plugins/camera/Camera.java | 880 ++++++----- .../flutter/plugins/camera/DartMessenger.java | 82 +- .../plugins/camera/MethodCallHandlerImpl.java | 29 +- .../plugins/camera/DartMessengerTest.java | 10 +- .../camera/camera/ios/Classes/CameraPlugin.m | 1388 +++++++++-------- packages/camera/camera/test/camera_test.dart | 3 - 6 files changed, 1206 insertions(+), 1186 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 0f47ad37785b..2a4f1f5ff008 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 @@ -30,7 +30,6 @@ import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.media.MediaRecorderBuilder; import io.flutter.view.TextureRegistry.SurfaceTextureEntry; - import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -42,479 +41,478 @@ import java.util.Map; public class Camera { - private final SurfaceTextureEntry flutterTexture; - private final CameraManager cameraManager; - private final OrientationEventListener orientationEventListener; - private final boolean isFrontFacing; - private final int sensorOrientation; - private final String cameraName; - private final Size captureSize; - private final Size previewSize; - private final boolean enableAudio; - - private CameraDevice cameraDevice; - private CameraCaptureSession cameraCaptureSession; - private ImageReader pictureImageReader; - private ImageReader imageStreamReader; - private DartMessenger dartMessenger; - private CaptureRequest.Builder captureRequestBuilder; - private MediaRecorder mediaRecorder; - private boolean recordingVideo; - private File videoRecordingFile; - private CamcorderProfile recordingProfile; - private int currentOrientation = ORIENTATION_UNKNOWN; - private Context applicationContext; - - // Mirrors camera.dart - public enum ResolutionPreset { - low, - medium, - high, - veryHigh, - ultraHigh, - max, + private final SurfaceTextureEntry flutterTexture; + private final CameraManager cameraManager; + private final OrientationEventListener orientationEventListener; + private final boolean isFrontFacing; + private final int sensorOrientation; + private final String cameraName; + private final Size captureSize; + private final Size previewSize; + private final boolean enableAudio; + + private CameraDevice cameraDevice; + private CameraCaptureSession cameraCaptureSession; + private ImageReader pictureImageReader; + private ImageReader imageStreamReader; + private DartMessenger dartMessenger; + private CaptureRequest.Builder captureRequestBuilder; + private MediaRecorder mediaRecorder; + private boolean recordingVideo; + private File videoRecordingFile; + private CamcorderProfile recordingProfile; + private int currentOrientation = ORIENTATION_UNKNOWN; + private Context applicationContext; + + // Mirrors camera.dart + public enum ResolutionPreset { + low, + medium, + high, + veryHigh, + ultraHigh, + max, + } + + public Camera( + final Activity activity, + final SurfaceTextureEntry flutterTexture, + final DartMessenger dartMessenger, + final String cameraName, + final String resolutionPreset, + final boolean enableAudio) + throws CameraAccessException { + if (activity == null) { + throw new IllegalStateException("No activity available!"); } - - public Camera( - final Activity activity, - final SurfaceTextureEntry flutterTexture, - final DartMessenger dartMessenger, - final String cameraName, - final String resolutionPreset, - final boolean enableAudio) - throws CameraAccessException { - if (activity == null) { - throw new IllegalStateException("No activity available!"); - } - this.cameraName = cameraName; - this.enableAudio = enableAudio; - this.flutterTexture = flutterTexture; - this.dartMessenger = dartMessenger; - this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); - this.applicationContext = activity.getApplicationContext(); - orientationEventListener = - new OrientationEventListener(activity.getApplicationContext()) { - @Override - public void onOrientationChanged(int i) { - if (i == ORIENTATION_UNKNOWN) { - return; - } - // Convert the raw deg angle to the nearest multiple of 90. - currentOrientation = (int) Math.round(i / 90.0) * 90; - } - }; - orientationEventListener.enable(); - - CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); - StreamConfigurationMap streamConfigurationMap = - characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - //noinspection ConstantConditions - sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); - //noinspection ConstantConditions - isFrontFacing = - characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT; - ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset); - recordingProfile = - CameraUtils.getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset); - captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); - previewSize = computeBestPreviewSize(cameraName, preset); + this.cameraName = cameraName; + this.enableAudio = enableAudio; + this.flutterTexture = flutterTexture; + this.dartMessenger = dartMessenger; + this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); + this.applicationContext = activity.getApplicationContext(); + orientationEventListener = + new OrientationEventListener(activity.getApplicationContext()) { + @Override + public void onOrientationChanged(int i) { + if (i == ORIENTATION_UNKNOWN) { + return; + } + // Convert the raw deg angle to the nearest multiple of 90. + currentOrientation = (int) Math.round(i / 90.0) * 90; + } + }; + orientationEventListener.enable(); + + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); + StreamConfigurationMap streamConfigurationMap = + characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + //noinspection ConstantConditions + sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + //noinspection ConstantConditions + isFrontFacing = + characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT; + ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset); + recordingProfile = + CameraUtils.getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset); + captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); + previewSize = computeBestPreviewSize(cameraName, preset); + } + + private void prepareMediaRecorder(String outputFilePath) throws IOException { + if (mediaRecorder != null) { + mediaRecorder.release(); } - private void prepareMediaRecorder(String outputFilePath) throws IOException { - if (mediaRecorder != null) { - mediaRecorder.release(); - } + mediaRecorder = + new MediaRecorderBuilder(recordingProfile, outputFilePath) + .setEnableAudio(enableAudio) + .setMediaOrientation(getMediaOrientation()) + .build(); + } + + @SuppressLint("MissingPermission") + public void open() throws CameraAccessException { + pictureImageReader = + ImageReader.newInstance( + captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2); + + // Used to steam image byte data to dart side. + imageStreamReader = + ImageReader.newInstance( + previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2); + + cameraManager.openCamera( + cameraName, + new CameraDevice.StateCallback() { + @Override + public void onOpened(@NonNull CameraDevice device) { + cameraDevice = device; + try { + startPreview(); + } catch (CameraAccessException e) { + dartMessenger.sendCameraErrorEvent(e.getMessage()); + close(); + return; + } - mediaRecorder = - new MediaRecorderBuilder(recordingProfile, outputFilePath) - .setEnableAudio(enableAudio) - .setMediaOrientation(getMediaOrientation()) - .build(); + dartMessenger.sendCameraInitializedEvent( + previewSize.getWidth(), previewSize.getHeight()); + } + + @Override + public void onClosed(@NonNull CameraDevice camera) { + dartMessenger.sendCameraClosingEvent(); + super.onClosed(camera); + } + + @Override + public void onDisconnected(@NonNull CameraDevice cameraDevice) { + close(); + dartMessenger.sendCameraErrorEvent("The camera was disconnected."); + } + + @Override + public void onError(@NonNull CameraDevice cameraDevice, int errorCode) { + close(); + String errorDescription; + switch (errorCode) { + case ERROR_CAMERA_IN_USE: + errorDescription = "The camera device is in use already."; + break; + case ERROR_MAX_CAMERAS_IN_USE: + errorDescription = "Max cameras in use"; + break; + case ERROR_CAMERA_DISABLED: + errorDescription = "The camera device could not be opened due to a device policy."; + break; + case ERROR_CAMERA_DEVICE: + errorDescription = "The camera device has encountered a fatal error"; + break; + case ERROR_CAMERA_SERVICE: + errorDescription = "The camera service has encountered a fatal error."; + break; + default: + errorDescription = "Unknown camera error"; + } + dartMessenger.sendCameraErrorEvent(errorDescription); + } + }, + null); + } + + private void writeToFile(ByteBuffer buffer, File file) throws IOException { + try (FileOutputStream outputStream = new FileOutputStream(file)) { + while (0 < buffer.remaining()) { + outputStream.getChannel().write(buffer); + } } - - @SuppressLint("MissingPermission") - public void open() throws CameraAccessException { - pictureImageReader = - ImageReader.newInstance( - captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2); - - // Used to steam image byte data to dart side. - imageStreamReader = - ImageReader.newInstance( - previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2); - - cameraManager.openCamera( - cameraName, - new CameraDevice.StateCallback() { - @Override - public void onOpened(@NonNull CameraDevice device) { - cameraDevice = device; - try { - startPreview(); - } catch (CameraAccessException e) { - dartMessenger.sendCameraErrorEvent(e.getMessage()); - close(); - return; - } - - dartMessenger.sendCameraInitializedEvent( - previewSize.getWidth(), - previewSize.getHeight()); - } - - @Override - public void onClosed(@NonNull CameraDevice camera) { - dartMessenger.sendCameraClosingEvent(); - super.onClosed(camera); - } - - @Override - public void onDisconnected(@NonNull CameraDevice cameraDevice) { - close(); - dartMessenger.sendCameraErrorEvent("The camera was disconnected."); - } - - @Override - public void onError(@NonNull CameraDevice cameraDevice, int errorCode) { - close(); - String errorDescription; - switch (errorCode) { - case ERROR_CAMERA_IN_USE: - errorDescription = "The camera device is in use already."; - break; - case ERROR_MAX_CAMERAS_IN_USE: - errorDescription = "Max cameras in use"; - break; - case ERROR_CAMERA_DISABLED: - errorDescription = "The camera device could not be opened due to a device policy."; - break; - case ERROR_CAMERA_DEVICE: - errorDescription = "The camera device has encountered a fatal error"; - break; - case ERROR_CAMERA_SERVICE: - errorDescription = "The camera service has encountered a fatal error."; - break; - default: - errorDescription = "Unknown camera error"; - } - dartMessenger.sendCameraErrorEvent(errorDescription); - } - }, - null); + } + + SurfaceTextureEntry getFlutterTexture() { + return flutterTexture; + } + + public void takePicture(@NonNull final Result result) { + final File outputDir = applicationContext.getCacheDir(); + final File file; + try { + file = File.createTempFile("CAP", ".jpg", outputDir); + } catch (IOException e) { + result.error("cannotCreateFile", e.getMessage(), null); + return; } - private void writeToFile(ByteBuffer buffer, File file) throws IOException { - try (FileOutputStream outputStream = new FileOutputStream(file)) { - while (0 < buffer.remaining()) { - outputStream.getChannel().write(buffer); + pictureImageReader.setOnImageAvailableListener( + reader -> { + try (Image image = reader.acquireLatestImage()) { + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); + writeToFile(buffer, file); + result.success(file.getAbsolutePath()); + } catch (IOException e) { + result.error("IOError", "Failed saving image", null); + } + }, + null); + + try { + final CaptureRequest.Builder captureBuilder = + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); + captureBuilder.addTarget(pictureImageReader.getSurface()); + captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); + + cameraCaptureSession.capture( + captureBuilder.build(), + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureFailed( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureFailure failure) { + String reason; + switch (failure.getReason()) { + case CaptureFailure.REASON_ERROR: + reason = "An error happened in the framework"; + break; + case CaptureFailure.REASON_FLUSHED: + reason = "The capture has failed due to an abortCaptures() call"; + break; + default: + reason = "Unknown reason"; + } + result.error("captureFailure", reason, null); } - } + }, + null); + } catch (CameraAccessException e) { + result.error("cameraAccess", e.getMessage(), null); } - - SurfaceTextureEntry getFlutterTexture() { - return flutterTexture; + } + + private void createCaptureSession(int templateType, Surface... surfaces) + throws CameraAccessException { + createCaptureSession(templateType, null, surfaces); + } + + private void createCaptureSession( + int templateType, Runnable onSuccessCallback, Surface... surfaces) + throws CameraAccessException { + // Close any existing capture session. + closeCaptureSession(); + + // Create a new capture builder. + captureRequestBuilder = cameraDevice.createCaptureRequest(templateType); + + // Build Flutter surface to render to + SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture(); + surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); + Surface flutterSurface = new Surface(surfaceTexture); + captureRequestBuilder.addTarget(flutterSurface); + + List remainingSurfaces = Arrays.asList(surfaces); + if (templateType != CameraDevice.TEMPLATE_PREVIEW) { + // If it is not preview mode, add all surfaces as targets. + for (Surface surface : remainingSurfaces) { + captureRequestBuilder.addTarget(surface); + } } - public void takePicture(@NonNull final Result result) { - final File outputDir = applicationContext.getCacheDir(); - final File file; - try { - file = File.createTempFile("CAP", ".jpg", outputDir); - } catch (IOException e) { - result.error("cannotCreateFile", e.getMessage(), null); - return; - } - - pictureImageReader.setOnImageAvailableListener( - reader -> { - try (Image image = reader.acquireLatestImage()) { - ByteBuffer buffer = image.getPlanes()[0].getBuffer(); - writeToFile(buffer, file); - result.success(file.getAbsolutePath()); - } catch (IOException e) { - result.error("IOError", "Failed saving image", null); - } - }, - null); - - try { - final CaptureRequest.Builder captureBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); - captureBuilder.addTarget(pictureImageReader.getSurface()); - captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); - - cameraCaptureSession.capture( - captureBuilder.build(), - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureFailed( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull CaptureFailure failure) { - String reason; - switch (failure.getReason()) { - case CaptureFailure.REASON_ERROR: - reason = "An error happened in the framework"; - break; - case CaptureFailure.REASON_FLUSHED: - reason = "The capture has failed due to an abortCaptures() call"; - break; - default: - reason = "Unknown reason"; - } - result.error("captureFailure", reason, null); - } - }, - null); - } catch (CameraAccessException e) { - result.error("cameraAccess", e.getMessage(), null); - } + // Prepare the callback + CameraCaptureSession.StateCallback callback = + new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(@NonNull CameraCaptureSession session) { + try { + if (cameraDevice == null) { + dartMessenger.sendCameraErrorEvent("The camera was closed during configuration."); + return; + } + cameraCaptureSession = session; + captureRequestBuilder.set( + CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); + cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); + if (onSuccessCallback != null) { + onSuccessCallback.run(); + } + } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) { + dartMessenger.sendCameraErrorEvent(e.getMessage()); + } + } + + @Override + public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { + dartMessenger.sendCameraErrorEvent("Failed to configure camera session."); + } + }; + + // Collect all surfaces we want to render to. + List surfaceList = new ArrayList<>(); + surfaceList.add(flutterSurface); + surfaceList.addAll(remainingSurfaces); + // Start the session + cameraDevice.createCaptureSession(surfaceList, callback, null); + } + + public void startVideoRecording(Result result) { + final File outputDir = applicationContext.getCacheDir(); + try { + videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir); + } catch (IOException e) { + result.error("cannotCreateFile", e.getMessage(), null); + return; } - private void createCaptureSession(int templateType, Surface... surfaces) - throws CameraAccessException { - createCaptureSession(templateType, null, surfaces); + try { + prepareMediaRecorder(videoRecordingFile.getAbsolutePath()); + recordingVideo = true; + createCaptureSession( + CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); + result.success(null); + } catch (CameraAccessException | IOException e) { + recordingVideo = false; + videoRecordingFile = null; + result.error("videoRecordingFailed", e.getMessage(), null); } + } - private void createCaptureSession( - int templateType, Runnable onSuccessCallback, Surface... surfaces) - throws CameraAccessException { - // Close any existing capture session. - closeCaptureSession(); - - // Create a new capture builder. - captureRequestBuilder = cameraDevice.createCaptureRequest(templateType); - - // Build Flutter surface to render to - SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture(); - surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); - Surface flutterSurface = new Surface(surfaceTexture); - captureRequestBuilder.addTarget(flutterSurface); - - List remainingSurfaces = Arrays.asList(surfaces); - if (templateType != CameraDevice.TEMPLATE_PREVIEW) { - // If it is not preview mode, add all surfaces as targets. - for (Surface surface : remainingSurfaces) { - captureRequestBuilder.addTarget(surface); - } - } - - // Prepare the callback - CameraCaptureSession.StateCallback callback = - new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(@NonNull CameraCaptureSession session) { - try { - if (cameraDevice == null) { - dartMessenger.sendCameraErrorEvent("The camera was closed during configuration."); - return; - } - cameraCaptureSession = session; - captureRequestBuilder.set( - CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); - cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); - if (onSuccessCallback != null) { - onSuccessCallback.run(); - } - } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) { - dartMessenger.sendCameraErrorEvent(e.getMessage()); - } - } - - @Override - public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { - dartMessenger.sendCameraErrorEvent("Failed to configure camera session."); - } - }; - - // Collect all surfaces we want to render to. - List surfaceList = new ArrayList<>(); - surfaceList.add(flutterSurface); - surfaceList.addAll(remainingSurfaces); - // Start the session - cameraDevice.createCaptureSession(surfaceList, callback, null); + public void stopVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; } - public void startVideoRecording(Result result) { - final File outputDir = applicationContext.getCacheDir(); - try { - videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir); - } catch (IOException e) { - result.error("cannotCreateFile", e.getMessage(), null); - return; - } - - try { - prepareMediaRecorder(videoRecordingFile.getAbsolutePath()); - recordingVideo = true; - createCaptureSession( - CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); - result.success(null); - } catch (CameraAccessException | IOException e) { - recordingVideo = false; - videoRecordingFile = null; - result.error("videoRecordingFailed", e.getMessage(), null); - } + try { + recordingVideo = false; + mediaRecorder.stop(); + mediaRecorder.reset(); + startPreview(); + result.success(videoRecordingFile.getAbsolutePath()); + videoRecordingFile = null; + } catch (CameraAccessException | IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); } + } - public void stopVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; - } - - try { - recordingVideo = false; - mediaRecorder.stop(); - mediaRecorder.reset(); - startPreview(); - result.success(videoRecordingFile.getAbsolutePath()); - videoRecordingFile = null; - } catch (CameraAccessException | IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - } + public void pauseVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; } - public void pauseVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; - } - - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mediaRecorder.pause(); - } else { - result.error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); - return; - } - } catch (IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - return; - } - - result.success(null); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mediaRecorder.pause(); + } else { + result.error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); + return; + } + } catch (IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + return; } - public void resumeVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; - } - - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mediaRecorder.resume(); - } else { - result.error( - "videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); - return; - } - } catch (IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - return; - } + result.success(null); + } - result.success(null); + public void resumeVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; } - public void startPreview() throws CameraAccessException { - if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; - - createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mediaRecorder.resume(); + } else { + result.error( + "videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); + return; + } + } catch (IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + return; } - public void startPreviewWithImageStream(EventChannel imageStreamChannel) - throws CameraAccessException { - createCaptureSession(CameraDevice.TEMPLATE_RECORD, imageStreamReader.getSurface()); - - imageStreamChannel.setStreamHandler( - new EventChannel.StreamHandler() { - @Override - public void onListen(Object o, EventChannel.EventSink imageStreamSink) { - setImageStreamImageAvailableListener(imageStreamSink); - } - - @Override - public void onCancel(Object o) { - imageStreamReader.setOnImageAvailableListener(null, null); - } - }); + result.success(null); + } + + public void startPreview() throws CameraAccessException { + if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; + + createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); + } + + public void startPreviewWithImageStream(EventChannel imageStreamChannel) + throws CameraAccessException { + createCaptureSession(CameraDevice.TEMPLATE_RECORD, imageStreamReader.getSurface()); + + imageStreamChannel.setStreamHandler( + new EventChannel.StreamHandler() { + @Override + public void onListen(Object o, EventChannel.EventSink imageStreamSink) { + setImageStreamImageAvailableListener(imageStreamSink); + } + + @Override + public void onCancel(Object o) { + imageStreamReader.setOnImageAvailableListener(null, null); + } + }); + } + + private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) { + imageStreamReader.setOnImageAvailableListener( + reader -> { + Image img = reader.acquireLatestImage(); + if (img == null) return; + + List> planes = new ArrayList<>(); + for (Image.Plane plane : img.getPlanes()) { + ByteBuffer buffer = plane.getBuffer(); + + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes, 0, bytes.length); + + Map planeBuffer = new HashMap<>(); + planeBuffer.put("bytesPerRow", plane.getRowStride()); + planeBuffer.put("bytesPerPixel", plane.getPixelStride()); + planeBuffer.put("bytes", bytes); + + planes.add(planeBuffer); + } + + Map imageBuffer = new HashMap<>(); + imageBuffer.put("width", img.getWidth()); + imageBuffer.put("height", img.getHeight()); + imageBuffer.put("format", img.getFormat()); + imageBuffer.put("planes", planes); + + imageStreamSink.success(imageBuffer); + img.close(); + }, + null); + } + + private void closeCaptureSession() { + if (cameraCaptureSession != null) { + cameraCaptureSession.close(); + cameraCaptureSession = null; } + } - private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) { - imageStreamReader.setOnImageAvailableListener( - reader -> { - Image img = reader.acquireLatestImage(); - if (img == null) return; - - List> planes = new ArrayList<>(); - for (Image.Plane plane : img.getPlanes()) { - ByteBuffer buffer = plane.getBuffer(); - - byte[] bytes = new byte[buffer.remaining()]; - buffer.get(bytes, 0, bytes.length); - - Map planeBuffer = new HashMap<>(); - planeBuffer.put("bytesPerRow", plane.getRowStride()); - planeBuffer.put("bytesPerPixel", plane.getPixelStride()); - planeBuffer.put("bytes", bytes); - - planes.add(planeBuffer); - } - - Map imageBuffer = new HashMap<>(); - imageBuffer.put("width", img.getWidth()); - imageBuffer.put("height", img.getHeight()); - imageBuffer.put("format", img.getFormat()); - imageBuffer.put("planes", planes); - - imageStreamSink.success(imageBuffer); - img.close(); - }, - null); - } + public void close() { + closeCaptureSession(); - private void closeCaptureSession() { - if (cameraCaptureSession != null) { - cameraCaptureSession.close(); - cameraCaptureSession = null; - } + if (cameraDevice != null) { + cameraDevice.close(); + cameraDevice = null; } - - public void close() { - closeCaptureSession(); - - if (cameraDevice != null) { - cameraDevice.close(); - cameraDevice = null; - } - if (pictureImageReader != null) { - pictureImageReader.close(); - pictureImageReader = null; - } - if (imageStreamReader != null) { - imageStreamReader.close(); - imageStreamReader = null; - } - if (mediaRecorder != null) { - mediaRecorder.reset(); - mediaRecorder.release(); - mediaRecorder = null; - } + if (pictureImageReader != null) { + pictureImageReader.close(); + pictureImageReader = null; } - - public void dispose() { - close(); - flutterTexture.release(); - orientationEventListener.disable(); + if (imageStreamReader != null) { + imageStreamReader.close(); + imageStreamReader = null; } - - private int getMediaOrientation() { - final int sensorOrientationOffset = - (currentOrientation == ORIENTATION_UNKNOWN) - ? 0 - : (isFrontFacing) ? -currentOrientation : currentOrientation; - return (sensorOrientationOffset + sensorOrientation + 360) % 360; + if (mediaRecorder != null) { + mediaRecorder.reset(); + mediaRecorder.release(); + mediaRecorder = null; } + } + + public void dispose() { + close(); + flutterTexture.release(); + orientationEventListener.disable(); + } + + private int getMediaOrientation() { + final int sensorOrientationOffset = + (currentOrientation == ORIENTATION_UNKNOWN) + ? 0 + : (isFrontFacing) ? -currentOrientation : currentOrientation; + return (sensorOrientationOffset + sensorOrientation + 360) % 360; + } } 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 89939a3e2fb0..49f9d9a76de0 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 @@ -1,54 +1,58 @@ package io.flutter.plugins.camera; import android.text.TextUtils; - import androidx.annotation.Nullable; - -import java.util.HashMap; -import java.util.Map; - import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodChannel; +import java.util.HashMap; +import java.util.Map; class DartMessenger { - @Nullable - private MethodChannel channel; - - enum EventType { - ERROR, - CAMERA_CLOSING, - INITIALIZED, - } - - DartMessenger(BinaryMessenger messenger, long cameraId) { - channel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); - } - - void sendCameraInitializedEvent(Integer previewWidth, Integer previewHeight) { - this.send(EventType.INITIALIZED, new HashMap() {{ + @Nullable private MethodChannel channel; + + enum EventType { + ERROR, + CAMERA_CLOSING, + INITIALIZED, + } + + DartMessenger(BinaryMessenger messenger, long cameraId) { + channel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); + } + + void sendCameraInitializedEvent(Integer previewWidth, Integer previewHeight) { + this.send( + EventType.INITIALIZED, + new HashMap() { + { if (previewWidth != null) put("previewWidth", previewWidth.doubleValue()); if (previewHeight != null) put("previewHeight", previewHeight.doubleValue()); - }}); - } - - void sendCameraClosingEvent() { - send(EventType.CAMERA_CLOSING); - } - - void sendCameraErrorEvent(@Nullable String description) { - this.send(EventType.ERROR, new HashMap() {{ + } + }); + } + + void sendCameraClosingEvent() { + send(EventType.CAMERA_CLOSING); + } + + void sendCameraErrorEvent(@Nullable String description) { + this.send( + EventType.ERROR, + new HashMap() { + { if (!TextUtils.isEmpty(description)) put("description", description); - }}); - } + } + }); + } - void send(EventType eventType) { - send(eventType, new HashMap<>()); - } + void send(EventType eventType) { + send(eventType, new HashMap<>()); + } - void send(EventType eventType, Map args) { - if (channel == null) { - return; - } - channel.invokeMethod(eventType.toString().toLowerCase(), args); + void send(EventType eventType, Map args) { + if (channel == null) { + return; } + channel.invokeMethod(eventType.toString().toLowerCase(), args); + } } 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 c2125a475dca..6c2e65e76f9e 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 @@ -74,22 +74,23 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) }); break; } - case "initialize": { - if (camera != null) { - try { - camera.open(); - result.success(null); - } catch (Exception e) { - handleException(e, result); + case "initialize": + { + if (camera != null) { + try { + camera.open(); + result.success(null); + } catch (Exception e) { + handleException(e, result); + } + } else { + result.error( + "cameraNotFound", + "Camera not found. Please call the 'create' method before calling 'initialize'.", + null); } - } else { - result.error( - "cameraNotFound", - "Camera not found. Please call the 'create' method before calling 'initialize'.", - null); + break; } - break; - } case "takePicture": { camera.takePicture(result); diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index b203e9bb1888..e86688860e13 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -30,8 +30,7 @@ public void send(@NonNull String channel, ByteBuffer message, BinaryReply callba } @Override - public void setMessageHandler(@NonNull String channel, BinaryMessageHandler handler) { - } + public void setMessageHandler(@NonNull String channel, BinaryMessageHandler handler) {} List getMessages() { return new ArrayList<>(sentMessages); @@ -66,8 +65,8 @@ public void sendCameraInitializedEvent_includesPreviewSize() { assertEquals(1, sentMessages.size()); MethodCall call = decodeSentMessage(sentMessages.get(0)); assertEquals(EventType.INITIALIZED.toString().toLowerCase(), call.method); - assertEquals(0, (double)call.argument("previewWidth"), 0); - assertEquals(0, (double)call.argument("previewHeight"), 0); + assertEquals(0, (double) call.argument("previewWidth"), 0); + assertEquals(0, (double) call.argument("previewHeight"), 0); } @Test @@ -77,8 +76,7 @@ public void sendCameraClosingEvent() { List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); MethodCall call = decodeSentMessage(sentMessages.get(0)); - assertEquals( - DartMessenger.EventType.CAMERA_CLOSING.toString().toLowerCase(), call.method); + assertEquals(DartMessenger.EventType.CAMERA_CLOSING.toString().toLowerCase(), call.method); assertNull(call.argument("description")); } diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 72acbb1369b7..dbff59e64f9a 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -10,9 +10,9 @@ #import static FlutterError *getFlutterError(NSError *error) { - return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] - message:error.localizedDescription - details:error.domain]; + return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] + message:error.localizedDescription + details:error.domain]; } @interface FLTSavePhotoDelegate : NSObject @@ -29,132 +29,131 @@ @interface FLTImageStreamHandler : NSObject @implementation FLTImageStreamHandler - (FlutterError *_Nullable)onCancelWithArguments:(id _Nullable)arguments { - _eventSink = nil; - return nil; + _eventSink = nil; + return nil; } - (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { - _eventSink = events; - return nil; + _eventSink = events; + return nil; } @end @implementation FLTSavePhotoDelegate { - /// Used to keep the delegate alive until didFinishProcessingPhotoSampleBuffer. - FLTSavePhotoDelegate *selfReference; + /// Used to keep the delegate alive until didFinishProcessingPhotoSampleBuffer. + FLTSavePhotoDelegate *selfReference; } - initWithPath:(NSString *)path - result:(FlutterResult)result - motionManager:(CMMotionManager *)motionManager - cameraPosition:(AVCaptureDevicePosition)cameraPosition { - self = [super init]; - NSAssert(self, @"super init cannot be nil"); - _path = path; - _motionManager = motionManager; - _cameraPosition = cameraPosition; - selfReference = self; - _result = result; - return self; + result:(FlutterResult)result + motionManager:(CMMotionManager *)motionManager + cameraPosition:(AVCaptureDevicePosition)cameraPosition { + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _path = path; + _motionManager = motionManager; + _cameraPosition = cameraPosition; + selfReference = self; + _result = result; + return self; } - (void)captureOutput:(AVCapturePhotoOutput *)output -didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer -previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer - resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings - bracketSettings:(AVCaptureBracketedStillImageSettings *)bracketSettings - error:(NSError *)error API_AVAILABLE(ios(10)) { - selfReference = nil; - if (error) { - _result(getFlutterError(error)); - return; - } - NSData *data = [AVCapturePhotoOutput - JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer - previewPhotoSampleBuffer:previewPhotoSampleBuffer]; - UIImage *image = [UIImage imageWithCGImage:[UIImage imageWithData:data].CGImage - scale:1.0 - orientation:[self getImageRotation]]; - // TODO(sigurdm): Consider writing file asynchronously. - bool success = [UIImageJPEGRepresentation(image, 1.0) writeToFile:_path atomically:YES]; - if (!success) { - _result([FlutterError errorWithCode:@"IOError" message:@"Unable to write file" details:nil]); - return; - } - _result(_path); + didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer + previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer + resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings + bracketSettings:(AVCaptureBracketedStillImageSettings *)bracketSettings + error:(NSError *)error API_AVAILABLE(ios(10)) { + selfReference = nil; + if (error) { + _result(getFlutterError(error)); + return; + } + NSData *data = [AVCapturePhotoOutput + JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer + previewPhotoSampleBuffer:previewPhotoSampleBuffer]; + UIImage *image = [UIImage imageWithCGImage:[UIImage imageWithData:data].CGImage + scale:1.0 + orientation:[self getImageRotation]]; + // TODO(sigurdm): Consider writing file asynchronously. + bool success = [UIImageJPEGRepresentation(image, 1.0) writeToFile:_path atomically:YES]; + if (!success) { + _result([FlutterError errorWithCode:@"IOError" message:@"Unable to write file" details:nil]); + return; + } + _result(_path); } - (UIImageOrientation)getImageRotation { - float const threshold = 45.0; - BOOL (^isNearValue)(float value1, float value2) = ^BOOL(float value1, float value2) { - return fabsf(value1 - value2) < threshold; - }; - BOOL (^isNearValueABS)(float value1, float value2) = ^BOOL(float value1, float value2) { - return isNearValue(fabsf(value1), fabsf(value2)); - }; - float yxAtan = (atan2(_motionManager.accelerometerData.acceleration.y, - _motionManager.accelerometerData.acceleration.x)) * - 180 / M_PI; - if (isNearValue(-90.0, yxAtan)) { - return UIImageOrientationRight; - } else if (isNearValueABS(180.0, yxAtan)) { - return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationUp - : UIImageOrientationDown; - } else if (isNearValueABS(0.0, yxAtan)) { - return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationDown /*rotate 180* */ - : UIImageOrientationUp /*do not rotate*/; - } else if (isNearValue(90.0, yxAtan)) { - return UIImageOrientationLeft; - } - // If none of the above, then the device is likely facing straight down or straight up -- just - // pick something arbitrary - // TODO: Maybe use the UIInterfaceOrientation if in these scenarios - return UIImageOrientationUp; + float const threshold = 45.0; + BOOL (^isNearValue)(float value1, float value2) = ^BOOL(float value1, float value2) { + return fabsf(value1 - value2) < threshold; + }; + BOOL (^isNearValueABS)(float value1, float value2) = ^BOOL(float value1, float value2) { + return isNearValue(fabsf(value1), fabsf(value2)); + }; + float yxAtan = (atan2(_motionManager.accelerometerData.acceleration.y, + _motionManager.accelerometerData.acceleration.x)) * + 180 / M_PI; + if (isNearValue(-90.0, yxAtan)) { + return UIImageOrientationRight; + } else if (isNearValueABS(180.0, yxAtan)) { + return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationUp + : UIImageOrientationDown; + } else if (isNearValueABS(0.0, yxAtan)) { + return _cameraPosition == AVCaptureDevicePositionBack ? UIImageOrientationDown /*rotate 180* */ + : UIImageOrientationUp /*do not rotate*/; + } else if (isNearValue(90.0, yxAtan)) { + return UIImageOrientationLeft; + } + // If none of the above, then the device is likely facing straight down or straight up -- just + // pick something arbitrary + // TODO: Maybe use the UIInterfaceOrientation if in these scenarios + return UIImageOrientationUp; } @end // Mirrors ResolutionPreset in camera.dart typedef enum { - veryLow, - low, - medium, - high, - veryHigh, - ultraHigh, - max, + veryLow, + low, + medium, + high, + veryHigh, + ultraHigh, + max, } ResolutionPreset; static ResolutionPreset getResolutionPresetForString(NSString *preset) { - if ([preset isEqualToString:@"veryLow"]) { - return veryLow; - } else if ([preset isEqualToString:@"low"]) { - return low; - } else if ([preset isEqualToString:@"medium"]) { - return medium; - } else if ([preset isEqualToString:@"high"]) { - return high; - } else if ([preset isEqualToString:@"veryHigh"]) { - return veryHigh; - } else if ([preset isEqualToString:@"ultraHigh"]) { - return ultraHigh; - } else if ([preset isEqualToString:@"max"]) { - return max; - } else { - NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain - code:NSURLErrorUnknown - userInfo:@{ - NSLocalizedDescriptionKey : [NSString - stringWithFormat:@"Unknown resolution preset %@", preset] - }]; - @throw error; - } + if ([preset isEqualToString:@"veryLow"]) { + return veryLow; + } else if ([preset isEqualToString:@"low"]) { + return low; + } else if ([preset isEqualToString:@"medium"]) { + return medium; + } else if ([preset isEqualToString:@"high"]) { + return high; + } else if ([preset isEqualToString:@"veryHigh"]) { + return veryHigh; + } else if ([preset isEqualToString:@"ultraHigh"]) { + return ultraHigh; + } else if ([preset isEqualToString:@"max"]) { + return max; + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown resolution preset %@", preset] + }]; + @throw error; + } } @interface FLTCam : NSObject + AVCaptureVideoDataOutputSampleBufferDelegate, + AVCaptureAudioDataOutputSampleBufferDelegate> @property(readonly, nonatomic) int64_t textureId; @property(nonatomic, copy) void (^onFrameAvailable)(void); @property BOOL enableAudio; @@ -191,7 +190,7 @@ @interface FLTCam : NSObject 0) { + currentSampleTime = CMTimeAdd(currentSampleTime, dur); + } + + if (_audioIsDisconnected) { + _audioIsDisconnected = NO; + + if (_audioTimeOffset.value == 0) { + _audioTimeOffset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); } else { - CMTime dur = CMSampleBufferGetDuration(sampleBuffer); - - if (dur.value > 0) { - currentSampleTime = CMTimeAdd(currentSampleTime, dur); - } - - if (_audioIsDisconnected) { - _audioIsDisconnected = NO; - - if (_audioTimeOffset.value == 0) { - _audioTimeOffset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); - } else { - CMTime offset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); - _audioTimeOffset = CMTimeAdd(_audioTimeOffset, offset); - } - - return; - } - - _lastAudioSampleTime = currentSampleTime; - - if (_audioTimeOffset.value != 0) { - CFRelease(sampleBuffer); - sampleBuffer = [self adjustTime:sampleBuffer by:_audioTimeOffset]; - } - - [self newAudioSample:sampleBuffer]; + CMTime offset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); + _audioTimeOffset = CMTimeAdd(_audioTimeOffset, offset); } - + + return; + } + + _lastAudioSampleTime = currentSampleTime; + + if (_audioTimeOffset.value != 0) { CFRelease(sampleBuffer); + sampleBuffer = [self adjustTime:sampleBuffer by:_audioTimeOffset]; + } + + [self newAudioSample:sampleBuffer]; } + + CFRelease(sampleBuffer); + } } - (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset CF_RETURNS_RETAINED { - CMItemCount count; - CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count); - CMSampleTimingInfo *pInfo = malloc(sizeof(CMSampleTimingInfo) * count); - CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count); - for (CMItemCount i = 0; i < count; i++) { - pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset); - pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset); - } - CMSampleBufferRef sout; - CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout); - free(pInfo); - return sout; + CMItemCount count; + CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count); + CMSampleTimingInfo *pInfo = malloc(sizeof(CMSampleTimingInfo) * count); + CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count); + for (CMItemCount i = 0; i < count; i++) { + pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset); + pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset); + } + CMSampleBufferRef sout; + CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout); + free(pInfo); + return sout; } - (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { - if (_videoWriter.status != AVAssetWriterStatusWriting) { - if (_videoWriter.status == AVAssetWriterStatusFailed) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; - } - return; + if (_videoWriter.status != AVAssetWriterStatusWriting) { + if (_videoWriter.status == AVAssetWriterStatusFailed) { + [_methodChannel invokeMethod:@"error" + arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; } - if (_videoWriterInput.readyForMoreMediaData) { - if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to video input"]]; - } + return; + } + if (_videoWriterInput.readyForMoreMediaData) { + if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { + [_methodChannel + invokeMethod:@"error" + arguments:[NSString stringWithFormat:@"%@", @"Unable to write to video input"]]; } + } } - (void)newAudioSample:(CMSampleBufferRef)sampleBuffer { - if (_videoWriter.status != AVAssetWriterStatusWriting) { - if (_videoWriter.status == AVAssetWriterStatusFailed) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; - } - return; + if (_videoWriter.status != AVAssetWriterStatusWriting) { + if (_videoWriter.status == AVAssetWriterStatusFailed) { + [_methodChannel invokeMethod:@"error" + arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; } - if (_audioWriterInput.readyForMoreMediaData) { - if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { - [_methodChannel invokeMethod:@"error" arguments:[NSString stringWithFormat:@"%@", @"Unable to write to audio input"]]; - } + return; + } + if (_audioWriterInput.readyForMoreMediaData) { + if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { + [_methodChannel + invokeMethod:@"error" + arguments:[NSString stringWithFormat:@"%@", @"Unable to write to audio input"]]; } + } } - (void)close { - [_captureSession stopRunning]; - for (AVCaptureInput *input in [_captureSession inputs]) { - [_captureSession removeInput:input]; - } - for (AVCaptureOutput *output in [_captureSession outputs]) { - [_captureSession removeOutput:output]; - } + [_captureSession stopRunning]; + for (AVCaptureInput *input in [_captureSession inputs]) { + [_captureSession removeInput:input]; + } + for (AVCaptureOutput *output in [_captureSession outputs]) { + [_captureSession removeOutput:output]; + } } - (void)dealloc { - if (_latestPixelBuffer) { - CFRelease(_latestPixelBuffer); - } - [_motionManager stopAccelerometerUpdates]; + if (_latestPixelBuffer) { + CFRelease(_latestPixelBuffer); + } + [_motionManager stopAccelerometerUpdates]; } - (CVPixelBufferRef)copyPixelBuffer { - CVPixelBufferRef pixelBuffer = _latestPixelBuffer; - while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) { - pixelBuffer = _latestPixelBuffer; - } - - return pixelBuffer; + CVPixelBufferRef pixelBuffer = _latestPixelBuffer; + while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) { + pixelBuffer = _latestPixelBuffer; + } + + return pixelBuffer; } - (void)startVideoRecordingWithResult:(FlutterResult)result { - if (!_isRecording) { - NSError *error; - _videoRecordingPath = [self getTemporaryFilePathWithExtension:@"mp4" subfolder:@"videos" prefix:@"CAP_" error:error]; - if (error) { - result(getFlutterError(error)); - return; - } - if (![self setupWriterForPath:_videoRecordingPath]) { - result([FlutterError errorWithCode:@"IOError" message:@"Setup Writer Failed" details:nil]); - return; - } - _isRecording = YES; - _isRecordingPaused = NO; - _videoTimeOffset = CMTimeMake(0, 1); - _audioTimeOffset = CMTimeMake(0, 1); - _videoIsDisconnected = NO; - _audioIsDisconnected = NO; - result(nil); - } else { - result([FlutterError errorWithCode:@"Error" message:@"Video is already recording" details:nil]); + if (!_isRecording) { + NSError *error; + _videoRecordingPath = [self getTemporaryFilePathWithExtension:@"mp4" + subfolder:@"videos" + prefix:@"CAP_" + error:error]; + if (error) { + result(getFlutterError(error)); + return; } + if (![self setupWriterForPath:_videoRecordingPath]) { + result([FlutterError errorWithCode:@"IOError" message:@"Setup Writer Failed" details:nil]); + return; + } + _isRecording = YES; + _isRecordingPaused = NO; + _videoTimeOffset = CMTimeMake(0, 1); + _audioTimeOffset = CMTimeMake(0, 1); + _videoIsDisconnected = NO; + _audioIsDisconnected = NO; + result(nil); + } else { + result([FlutterError errorWithCode:@"Error" message:@"Video is already recording" details:nil]); + } } - (void)stopVideoRecordingWithResult:(FlutterResult)result { - if (_isRecording) { - _isRecording = NO; - if (_videoWriter.status != AVAssetWriterStatusUnknown) { - [_videoWriter finishWritingWithCompletionHandler:^{ - if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { - result(self->_videoRecordingPath); - self->_videoRecordingPath = nil; - } else { - result([FlutterError errorWithCode:@"IOError" message:@"AVAssetWriter could not finish writing!" details:nil]); - } - }]; + if (_isRecording) { + _isRecording = NO; + if (_videoWriter.status != AVAssetWriterStatusUnknown) { + [_videoWriter finishWritingWithCompletionHandler:^{ + if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { + result(self->_videoRecordingPath); + self->_videoRecordingPath = nil; + } else { + result([FlutterError errorWithCode:@"IOError" + message:@"AVAssetWriter could not finish writing!" + details:nil]); } - } else { - NSError *error = + }]; + } + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorResourceUnavailable userInfo:@{NSLocalizedDescriptionKey : @"Video is not recording!"}]; - result(getFlutterError(error)); - } + result(getFlutterError(error)); + } } - (void)pauseVideoRecordingWithResult:(FlutterResult)result { - _isRecordingPaused = YES; - _videoIsDisconnected = YES; - _audioIsDisconnected = YES; - result(nil); + _isRecordingPaused = YES; + _videoIsDisconnected = YES; + _audioIsDisconnected = YES; + result(nil); } - (void)resumeVideoRecordingWithResult:(FlutterResult)result { - _isRecordingPaused = NO; - result(nil); + _isRecordingPaused = NO; + result(nil); } - (void)startImageStreamWithMessenger:(NSObject *)messenger { - if (!_isStreamingImages) { - FlutterEventChannel *eventChannel = + if (!_isStreamingImages) { + FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:@"plugins.flutter.io/camera/imageStream" binaryMessenger:messenger]; - - _imageStreamHandler = [[FLTImageStreamHandler alloc] init]; - [eventChannel setStreamHandler:_imageStreamHandler]; - - _isStreamingImages = YES; - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are already streaming!"]; - } + + _imageStreamHandler = [[FLTImageStreamHandler alloc] init]; + [eventChannel setStreamHandler:_imageStreamHandler]; + + _isStreamingImages = YES; + } else { + [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are already streaming!"]; + } } - (void)stopImageStream { - if (_isStreamingImages) { - _isStreamingImages = NO; - _imageStreamHandler = nil; - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are not streaming!"]; - } + if (_isStreamingImages) { + _isStreamingImages = NO; + _imageStreamHandler = nil; + } else { + [_methodChannel invokeMethod:@"error" arguments:@"Images from camera are not streaming!"]; + } } - (BOOL)setupWriterForPath:(NSString *)path { - NSError *error = nil; - NSURL *outputURL; - if (path != nil) { - outputURL = [NSURL fileURLWithPath:path]; - } else { - return NO; - } - if (_enableAudio && !_isAudioSetup) { - [self setUpCaptureSessionForAudio]; - } - _videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL - fileType:AVFileTypeQuickTimeMovie - error:&error]; - NSParameterAssert(_videoWriter); - if (error) { - [_methodChannel invokeMethod:@"error" arguments:error.description]; - return NO; - } - NSDictionary *videoSettings = [NSDictionary - dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey, + NSError *error = nil; + NSURL *outputURL; + if (path != nil) { + outputURL = [NSURL fileURLWithPath:path]; + } else { + return NO; + } + if (_enableAudio && !_isAudioSetup) { + [self setUpCaptureSessionForAudio]; + } + _videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL + fileType:AVFileTypeQuickTimeMovie + error:&error]; + NSParameterAssert(_videoWriter); + if (error) { + [_methodChannel invokeMethod:@"error" arguments:error.description]; + return NO; + } + NSDictionary *videoSettings = [NSDictionary + dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey, [NSNumber numberWithInt:_previewSize.height], AVVideoWidthKey, [NSNumber numberWithInt:_previewSize.width], AVVideoHeightKey, nil]; - _videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo - outputSettings:videoSettings]; - - _videoAdaptor = [AVAssetWriterInputPixelBufferAdaptor - assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_videoWriterInput - sourcePixelBufferAttributes:@{ - (NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat) - }]; - - NSParameterAssert(_videoWriterInput); - _videoWriterInput.expectsMediaDataInRealTime = YES; - - // Add the audio input - if (_enableAudio) { - AudioChannelLayout acl; - bzero(&acl, sizeof(acl)); - acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; - NSDictionary *audioOutputSettings = nil; - // Both type of audio inputs causes output video file to be corrupted. - audioOutputSettings = [NSDictionary - dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kAudioFormatMPEG4AAC], AVFormatIDKey, - [NSNumber numberWithFloat:44100.0], AVSampleRateKey, - [NSNumber numberWithInt:1], AVNumberOfChannelsKey, - [NSData dataWithBytes:&acl length:sizeof(acl)], - AVChannelLayoutKey, nil]; - _audioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio - outputSettings:audioOutputSettings]; - _audioWriterInput.expectsMediaDataInRealTime = YES; - - [_videoWriter addInput:_audioWriterInput]; - [_audioOutput setSampleBufferDelegate:self queue:_dispatchQueue]; - } - - [_videoWriter addInput:_videoWriterInput]; - [_captureVideoOutput setSampleBufferDelegate:self queue:_dispatchQueue]; - - return YES; + _videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo + outputSettings:videoSettings]; + + _videoAdaptor = [AVAssetWriterInputPixelBufferAdaptor + assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_videoWriterInput + sourcePixelBufferAttributes:@{ + (NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat) + }]; + + NSParameterAssert(_videoWriterInput); + _videoWriterInput.expectsMediaDataInRealTime = YES; + + // Add the audio input + if (_enableAudio) { + AudioChannelLayout acl; + bzero(&acl, sizeof(acl)); + acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; + NSDictionary *audioOutputSettings = nil; + // Both type of audio inputs causes output video file to be corrupted. + audioOutputSettings = [NSDictionary + dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kAudioFormatMPEG4AAC], AVFormatIDKey, + [NSNumber numberWithFloat:44100.0], AVSampleRateKey, + [NSNumber numberWithInt:1], AVNumberOfChannelsKey, + [NSData dataWithBytes:&acl length:sizeof(acl)], + AVChannelLayoutKey, nil]; + _audioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio + outputSettings:audioOutputSettings]; + _audioWriterInput.expectsMediaDataInRealTime = YES; + + [_videoWriter addInput:_audioWriterInput]; + [_audioOutput setSampleBufferDelegate:self queue:_dispatchQueue]; + } + + [_videoWriter addInput:_videoWriterInput]; + [_captureVideoOutput setSampleBufferDelegate:self queue:_dispatchQueue]; + + return YES; } - (void)setUpCaptureSessionForAudio { - NSError *error = nil; - // Create a device input with the device and add it to the session. - // Setup the audio input. - AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; - AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice - error:&error]; - if (error) { - [_methodChannel invokeMethod:@"error" arguments:error.description]; - } - // Setup the audio output. - _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; - - if ([_captureSession canAddInput:audioInput]) { - [_captureSession addInput:audioInput]; - - if ([_captureSession canAddOutput:_audioOutput]) { - [_captureSession addOutput:_audioOutput]; - _isAudioSetup = YES; - } else { - [_methodChannel invokeMethod:@"error" arguments:@"Unable to add Audio input/output to session capture"]; - _isAudioSetup = NO; - } + NSError *error = nil; + // Create a device input with the device and add it to the session. + // Setup the audio input. + AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; + AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice + error:&error]; + if (error) { + [_methodChannel invokeMethod:@"error" arguments:error.description]; + } + // Setup the audio output. + _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; + + if ([_captureSession canAddInput:audioInput]) { + [_captureSession addInput:audioInput]; + + if ([_captureSession canAddOutput:_audioOutput]) { + [_captureSession addOutput:_audioOutput]; + _isAudioSetup = YES; + } else { + [_methodChannel invokeMethod:@"error" + arguments:@"Unable to add Audio input/output to session capture"]; + _isAudioSetup = NO; } + } } @end @@ -749,146 +771,146 @@ @interface CameraPlugin () @end @implementation CameraPlugin { - dispatch_queue_t _dispatchQueue; + dispatch_queue_t _dispatchQueue; } + (void)registerWithRegistrar:(NSObject *)registrar { - FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera" - binaryMessenger:[registrar messenger]]; - CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] - messenger:[registrar messenger]]; - [registrar addMethodCallDelegate:instance channel:channel]; + FlutterMethodChannel *channel = + [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera" + binaryMessenger:[registrar messenger]]; + CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] + messenger:[registrar messenger]]; + [registrar addMethodCallDelegate:instance channel:channel]; } - (instancetype)initWithRegistry:(NSObject *)registry messenger:(NSObject *)messenger { - self = [super init]; - NSAssert(self, @"super init cannot be nil"); - _registry = registry; - _messenger = messenger; - return self; + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _registry = registry; + _messenger = messenger; + return self; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if (_dispatchQueue == nil) { - _dispatchQueue = dispatch_queue_create("io.flutter.camera.dispatchqueue", NULL); - } - - // Invoke the plugin on another dispatch queue to avoid blocking the UI. - dispatch_async(_dispatchQueue, ^{ - [self handleMethodCallAsync:call result:result]; - }); + if (_dispatchQueue == nil) { + _dispatchQueue = dispatch_queue_create("io.flutter.camera.dispatchqueue", NULL); + } + + // Invoke the plugin on another dispatch queue to avoid blocking the UI. + dispatch_async(_dispatchQueue, ^{ + [self handleMethodCallAsync:call result:result]; + }); } - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([@"availableCameras" isEqualToString:call.method]) { - if (@available(iOS 10.0, *)) { - AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession - discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ] - mediaType:AVMediaTypeVideo - position:AVCaptureDevicePositionUnspecified]; - NSArray *devices = discoverySession.devices; - NSMutableArray *> *reply = - [[NSMutableArray alloc] initWithCapacity:devices.count]; - for (AVCaptureDevice *device in devices) { - NSString *lensFacing; - switch ([device position]) { - case AVCaptureDevicePositionBack: - lensFacing = @"back"; - break; - case AVCaptureDevicePositionFront: - lensFacing = @"front"; - break; - case AVCaptureDevicePositionUnspecified: - lensFacing = @"external"; - break; - } - [reply addObject:@{ - @"name" : [device uniqueID], - @"lensFacing" : lensFacing, - @"sensorOrientation" : @90, - }]; - } - result(reply); - } else { - result(FlutterMethodNotImplemented); - } - } else if ([@"create" isEqualToString:call.method]) { - NSString *cameraName = call.arguments[@"cameraName"]; - NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; - NSNumber *enableAudio = call.arguments[@"enableAudio"]; - NSError *error; - FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName - resolutionPreset:resolutionPreset - enableAudio:[enableAudio boolValue] - dispatchQueue:_dispatchQueue - error:&error]; - - if (error) { - result(getFlutterError(error)); - } else { - if (_camera) { - [_camera close]; - } - int64_t textureId = [_registry registerTexture:cam]; - _camera = cam; - - result(@{ - @"cameraId" : @(textureId), - }); + if ([@"availableCameras" isEqualToString:call.method]) { + if (@available(iOS 10.0, *)) { + AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession + discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ] + mediaType:AVMediaTypeVideo + position:AVCaptureDevicePositionUnspecified]; + NSArray *devices = discoverySession.devices; + NSMutableArray *> *reply = + [[NSMutableArray alloc] initWithCapacity:devices.count]; + for (AVCaptureDevice *device in devices) { + NSString *lensFacing; + switch ([device position]) { + case AVCaptureDevicePositionBack: + lensFacing = @"back"; + break; + case AVCaptureDevicePositionFront: + lensFacing = @"front"; + break; + case AVCaptureDevicePositionUnspecified: + lensFacing = @"external"; + break; } - } else if ([@"startImageStream" isEqualToString:call.method]) { - [_camera startImageStreamWithMessenger:_messenger]; - result(nil); - } else if ([@"stopImageStream" isEqualToString:call.method]) { - [_camera stopImageStream]; - result(nil); + [reply addObject:@{ + @"name" : [device uniqueID], + @"lensFacing" : lensFacing, + @"sensorOrientation" : @90, + }]; + } + result(reply); } else { - NSDictionary *argsMap = call.arguments; - NSUInteger cameraId = ((NSNumber *)argsMap[@"cameraId"]).unsignedIntegerValue; - if ([@"initialize" isEqualToString:call.method]) { - __weak CameraPlugin *weakSelf = self; - _camera.onFrameAvailable = ^{ - [weakSelf.registry textureFrameAvailable:cameraId]; - }; - FlutterMethodChannel *methodChannel = [FlutterMethodChannel - methodChannelWithName:[NSString - stringWithFormat:@"flutter.io/cameraPlugin/camera%lu", - (unsigned long) cameraId] - binaryMessenger:_messenger]; - _camera.methodChannel = methodChannel; - [methodChannel invokeMethod:@"initialized" arguments:@{ - @"previewWidth" : @(_camera.previewSize.width), - @"previewHeight" : @(_camera.previewSize.height) - }]; - [_camera start]; - result(nil); - } else if ([@"takePicture" isEqualToString:call.method]) { - if (@available(iOS 10.0, *)) { - [_camera captureToFile:result]; - } else { - result(FlutterMethodNotImplemented); - } - } else if ([@"dispose" isEqualToString:call.method]) { - [_registry unregisterTexture:cameraId]; - [_camera close]; - _dispatchQueue = nil; - result(nil); - } else if ([@"prepareForVideoRecording" isEqualToString:call.method]) { - [_camera setUpCaptureSessionForAudio]; - result(nil); - } else if ([@"startVideoRecording" isEqualToString:call.method]) { - [_camera startVideoRecordingWithResult:result]; - } else if ([@"stopVideoRecording" isEqualToString:call.method]) { - [_camera stopVideoRecordingWithResult:result]; - } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { - [_camera pauseVideoRecordingWithResult:result]; - } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { - [_camera resumeVideoRecordingWithResult:result]; - } else { - result(FlutterMethodNotImplemented); - } + result(FlutterMethodNotImplemented); + } + } else if ([@"create" isEqualToString:call.method]) { + NSString *cameraName = call.arguments[@"cameraName"]; + NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; + NSNumber *enableAudio = call.arguments[@"enableAudio"]; + NSError *error; + FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName + resolutionPreset:resolutionPreset + enableAudio:[enableAudio boolValue] + dispatchQueue:_dispatchQueue + error:&error]; + + if (error) { + result(getFlutterError(error)); + } else { + if (_camera) { + [_camera close]; + } + int64_t textureId = [_registry registerTexture:cam]; + _camera = cam; + + result(@{ + @"cameraId" : @(textureId), + }); + } + } else if ([@"startImageStream" isEqualToString:call.method]) { + [_camera startImageStreamWithMessenger:_messenger]; + result(nil); + } else if ([@"stopImageStream" isEqualToString:call.method]) { + [_camera stopImageStream]; + result(nil); + } else { + NSDictionary *argsMap = call.arguments; + NSUInteger cameraId = ((NSNumber *)argsMap[@"cameraId"]).unsignedIntegerValue; + if ([@"initialize" isEqualToString:call.method]) { + __weak CameraPlugin *weakSelf = self; + _camera.onFrameAvailable = ^{ + [weakSelf.registry textureFrameAvailable:cameraId]; + }; + FlutterMethodChannel *methodChannel = [FlutterMethodChannel + methodChannelWithName:[NSString stringWithFormat:@"flutter.io/cameraPlugin/camera%lu", + (unsigned long)cameraId] + binaryMessenger:_messenger]; + _camera.methodChannel = methodChannel; + [methodChannel invokeMethod:@"initialized" + arguments:@{ + @"previewWidth" : @(_camera.previewSize.width), + @"previewHeight" : @(_camera.previewSize.height) + }]; + [_camera start]; + result(nil); + } else if ([@"takePicture" isEqualToString:call.method]) { + if (@available(iOS 10.0, *)) { + [_camera captureToFile:result]; + } else { + result(FlutterMethodNotImplemented); + } + } else if ([@"dispose" isEqualToString:call.method]) { + [_registry unregisterTexture:cameraId]; + [_camera close]; + _dispatchQueue = nil; + result(nil); + } else if ([@"prepareForVideoRecording" isEqualToString:call.method]) { + [_camera setUpCaptureSessionForAudio]; + result(nil); + } else if ([@"startVideoRecording" isEqualToString:call.method]) { + [_camera startVideoRecordingWithResult:result]; + } else if ([@"stopVideoRecording" isEqualToString:call.method]) { + [_camera stopVideoRecordingWithResult:result]; + } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { + [_camera pauseVideoRecordingWithResult:result]; + } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { + [_camera resumeVideoRecordingWithResult:result]; + } else { + result(FlutterMethodNotImplemented); } + } } @end diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 7b3db21f4b34..32e454e99f96 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -133,8 +133,6 @@ void main() { await cameraController.initialize(); expect(cameraController.value.isInitialized, isTrue); - - }); }); } @@ -142,7 +140,6 @@ void main() { class MockCameraPlatform extends Mock with MockPlatformInterfaceMixin implements CameraPlatform { - @override Future> availableCameras() => Future.value(mockAvailableCameras); From 6b934300ec3950e313c3e245f3146ea1ad3b7420 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 27 Nov 2020 21:23:09 +0100 Subject: [PATCH 58/89] Fixed tests, formatting and analysis warnings --- .../example/integration_test/camera_test.dart | 24 +++------ packages/camera/camera/lib/camera.dart | 43 +++++++++++++-- packages/camera/camera/pubspec.yaml | 4 ++ packages/camera/camera/test/camera_test.dart | 52 +++++-------------- 4 files changed, 65 insertions(+), 58 deletions(-) diff --git a/packages/camera/camera/example/integration_test/camera_test.dart b/packages/camera/camera/example/integration_test/camera_test.dart index 146126963632..c2e73e0f1563 100644 --- a/packages/camera/camera/example/integration_test/camera_test.dart +++ b/packages/camera/camera/example/integration_test/camera_test.dart @@ -5,7 +5,6 @@ import 'dart:ui'; import 'package:camera/camera.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:camera/camera.dart'; import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; import 'package:integration_test/integration_test.dart'; @@ -56,12 +55,10 @@ void main() { 'Capturing photo at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}'); // Take Picture - final String filePath = - '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.jpg'; - await controller.takePicture(filePath); + final file = await controller.takePicture(); // Load picture - final File fileImage = File(filePath); + final File fileImage = File(file.path); final Image image = await decodeImageFromList(fileImage.readAsBytesSync()); // Verify image dimensions are as expected @@ -103,14 +100,12 @@ void main() { 'Capturing video at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}'); // Take Video - final String filePath = - '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.mp4'; - await controller.startVideoRecording(filePath); + await controller.startVideoRecording(); sleep(const Duration(milliseconds: 300)); - await controller.stopVideoRecording(); + final file = await controller.stopVideoRecording(); // Load video metadata - final File videoFile = File(filePath); + final File videoFile = File(file.path); final VideoPlayerController videoController = VideoPlayerController.file(videoFile); await videoController.initialize(); @@ -161,13 +156,10 @@ void main() { await controller.initialize(); await controller.prepareForVideoRecording(); - final String filePath = - '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.mp4'; - int startPause; int timePaused = 0; - await controller.startVideoRecording(filePath); + await controller.startVideoRecording(); final int recordingStart = DateTime.now().millisecondsSinceEpoch; sleep(const Duration(milliseconds: 500)); @@ -187,11 +179,11 @@ void main() { sleep(const Duration(milliseconds: 500)); - await controller.stopVideoRecording(); + final file = await controller.stopVideoRecording(); final int recordingTime = DateTime.now().millisecondsSinceEpoch - recordingStart; - final File videoFile = File(filePath); + final File videoFile = File(file.path); final VideoPlayerController videoController = VideoPlayerController.file( videoFile, ); diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index fa11c1108061..e313314ac36e 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -273,18 +273,40 @@ class CameraController extends ValueNotifier { /// Throws a [CameraException] if image streaming or video recording has /// already started. // TODO(bmparr): Add settings for resolution and fps. - Widget buildView() { + Future startImageStream(onLatestImageAvailable onAvailable) async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', - 'buildView() was called on uninitialized CameraController.', + 'startImageStream was called on uninitialized CameraController.', + ); + } + if (value.isRecordingVideo) { + throw CameraException( + 'A video recording is already started.', + 'startImageStream was called while a video is being recorded.', ); } + if (value.isStreamingImages) { + throw CameraException( + 'A camera has started streaming images.', + 'startImageStream was called while a camera was streaming images.', + ); + } + try { - return CameraPlatform.instance.buildView(_cameraId); + await _channel.invokeMethod('startImageStream'); + value = value.copyWith(isStreamingImages: true); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } + const EventChannel cameraEventChannel = + EventChannel('plugins.flutter.io/camera/imageStream'); + _imageStreamSubscription = + cameraEventChannel.receiveBroadcastStream().listen( + (dynamic imageData) { + onAvailable(CameraImage.fromPlatformData(imageData)); + }, + ); } /// Stop streaming images from platform camera. @@ -427,6 +449,21 @@ class CameraController extends ValueNotifier { } } + /// Returns a widget showing a live camera preview. + Widget buildView() { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'buildView() was called on uninitialized CameraController.', + ); + } + try { + return CameraPlatform.instance.buildView(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + /// Releases the resources of this camera. @override Future dispose() async { diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 433edaa54783..092534e66554 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -5,9 +5,13 @@ description: A Flutter plugin for getting information about and controlling the version: 0.5.8+16 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera +# TODO(mvanbeusekom): Remove when camera_platform_interface package is published. +publish_to: none + dependencies: flutter: sdk: flutter + # TODO(mvanbeusekom): Update dependency to pub.dev version once camera_platform_interface is published camera_platform_interface: path: ../camera_platform_interface diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 32e454e99f96..89d0d7cb88ff 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -5,13 +5,10 @@ import 'dart:async'; import 'dart:ui'; import 'package:camera/camera.dart'; -import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:pedantic/pedantic.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -import 'package:rxdart/rxdart.dart'; get mockAvailableCameras => [ CameraDescription( @@ -26,8 +23,7 @@ get mockAvailableCameras => [ get mockInitializeCamera => 13; -get mockOnResolutionChangedEvent => - ResolutionChangedEvent(13, 100, 100, 75, 75); +get mockOnCameraInitializedEvent => CameraInitializedEvent(13, 75, 75); get mockOnCameraClosingEvent => null; @@ -113,7 +109,7 @@ void main() { verify(CameraPlatform.instance.dispose(13)).called(1); }); - test('initialize() returns when disposed', () async { + test('initialize() throws CameraException when disposed', () async { CameraController cameraController = CameraController( CameraDescription( name: 'cam', @@ -129,10 +125,14 @@ void main() { await cameraController.dispose(); verify(CameraPlatform.instance.dispose(13)).called(1); - expect(cameraController.value.isInitialized, isFalse); - await cameraController.initialize(); - expect(cameraController.value.isInitialized, isTrue); + expect( + cameraController.initialize, + throwsA(isA().having( + (error) => error.description, + 'Error description', + 'initialize was called on a disposed CameraController', + ))); }); }); } @@ -145,16 +145,16 @@ class MockCameraPlatform extends Mock Future.value(mockAvailableCameras); @override - Future initializeCamera( - CameraDescription cameraDescription, + Future createCamera( + CameraDescription description, ResolutionPreset resolutionPreset, { bool enableAudio, }) => Future.value(mockInitializeCamera); @override - Stream onResolutionChanged(int cameraId) { - return Stream.value(mockOnResolutionChangedEvent); + Stream onCameraInitialized(int cameraId) { + return Stream.value(mockOnCameraInitializedEvent); } @override @@ -170,35 +170,9 @@ class MockCameraPlatform extends Mock @override Future takePicture(int cameraId) => Future.value(mockTakePicture); - // @override - // Future prepareForVideoRecording() { - // - // } - @override Future startVideoRecording(int cameraId) => Future.value(mockVideoRecordingXFile); - -// @override -// Future stopVideoRecording(int cameraId) { -// -// } - -// @override -// Future pauseVideoRecording(int cameraId) { -// throw UnimplementedError('pauseVideoRecording() is not implemented.'); -// } - -// @override -// Future resumeVideoRecording(int cameraId) { -// throw UnimplementedError('resumeVideoRecording() is not implemented.'); -// } - -// @override -// Widget buildView(int cameraId) { -// throw UnimplementedError('buildView() has not been implemented.'); -// } - } class MockCameraDescription extends CameraDescription { From 5d58410fd49a7f7d610f68151314f81a28bfc2df Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 30 Nov 2020 09:31:06 +0100 Subject: [PATCH 59/89] Added missing documentation public APIs --- .../lib/src/events/camera_event.dart | 16 ++++++++++++++++ .../method_channel/method_channel_camera.dart | 11 +++++++++++ 2 files changed, 27 insertions(+) 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 3746ae481584..b4c082617f66 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 @@ -52,11 +52,15 @@ class CameraInitializedEvent extends CameraEvent { this.previewHeight, ) : super(cameraId); + /// Converts the supplied [Map] to an instance of the [CameraInitializedEvent] + /// class. CameraInitializedEvent.fromJson(Map json) : previewWidth = json['previewWidth'], previewHeight = json['previewHeight'], super(json['cameraId']); + /// Converts the [CameraInitializedEvent] instance into a [Map] instance that + /// can be serialized to JSON. Map toJson() => { 'cameraId': cameraId, 'previewWidth': previewWidth, @@ -96,11 +100,15 @@ class CameraResolutionChangedEvent extends CameraEvent { this.captureHeight, ) : super(cameraId); + /// Converts the supplied [Map] to an instance of the + /// [CameraResolutionChangedEvent] class. CameraResolutionChangedEvent.fromJson(Map json) : captureWidth = json['captureWidth'], captureHeight = json['captureHeight'], super(json['cameraId']); + /// Converts the [CameraResolutionChangedEvent] instance into a [Map] instance + /// that can be serialized to JSON. Map toJson() => { 'cameraId': cameraId, 'captureWidth': captureWidth, @@ -127,9 +135,13 @@ class CameraClosingEvent extends CameraEvent { /// `cameraId`. CameraClosingEvent(int cameraId) : super(cameraId); + /// Converts the supplied [Map] to an instance of the [CameraClosingEvent] + /// class. CameraClosingEvent.fromJson(Map json) : super(json['cameraId']); + /// Converts the [CameraClosingEvent] instance into a [Map] instance that can + /// be serialized to JSON. Map toJson() => { 'cameraId': cameraId, }; @@ -156,10 +168,14 @@ class CameraErrorEvent extends CameraEvent { /// The `description` represents the error occured on the camera. CameraErrorEvent(int cameraId, this.description) : super(cameraId); + /// Converts the supplied [Map] to an instance of the [CameraErrorEvent] + /// class. CameraErrorEvent.fromJson(Map json) : description = json['description'], super(json['cameraId']); + /// Converts the [CameraErrorEvent] instance into a [Map] instance that can be + /// serialized to JSON. Map toJson() => { 'cameraId': cameraId, 'description': description, 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 c66994283876..288bfb996238 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 @@ -17,6 +17,13 @@ const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); class MethodChannelCamera extends CameraPlatform { final Map _channels = {}; + /// The controller we need to broadcast the different events coming + /// from handleMethodCall. + /// + /// It is a `broadcast` because multiple controllers will connect to + /// different stream views of this Controller. + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. @visibleForTesting final StreamController cameraEventStreamController = StreamController.broadcast(); @@ -203,6 +210,10 @@ class MethodChannelCamera extends CameraPlatform { throw ArgumentError('Unknown CameraLensDirection value'); } + /// Converts messages received from the native platform into events. + /// + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. @visibleForTesting Future handleMethodCall(MethodCall call, int cameraId) async { switch (call.method) { From 8ee04427ba3eba2d1a8f3cb81085d99a5bb07004 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 30 Nov 2020 09:37:44 +0100 Subject: [PATCH 60/89] Added missing license to Dart files --- packages/camera/camera/test/camera_test.dart | 1 + packages/camera/camera/test/camera_value_test.dart | 4 ++++ .../lib/src/events/camera_event.dart | 4 ++++ .../lib/src/types/resolution_preset.dart | 4 ++++ .../test/utils/method_channel_mock.dart | 4 ++++ 5 files changed, 17 insertions(+) diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 89d0d7cb88ff..66de0d9db5bf 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1,6 +1,7 @@ // Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. + import 'dart:async'; import 'dart:ui'; diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart index 4a70f059cb5e..28255eb0a568 100644 --- a/packages/camera/camera/test/camera_value_test.dart +++ b/packages/camera/camera/test/camera_value_test.dart @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + import 'dart:ui'; import 'package:camera/camera.dart'; 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 b4c082617f66..70158a720ac8 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 @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + /// Generic Event coming from the native side of Camera. /// /// All [CameraEvent]s contain the `cameraId` that originated the event. This diff --git a/packages/camera/camera_platform_interface/lib/src/types/resolution_preset.dart b/packages/camera/camera_platform_interface/lib/src/types/resolution_preset.dart index 2ffb718efa17..ead592364131 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/resolution_preset.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/resolution_preset.dart @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + /// Affect the quality of video recording and image capture: /// /// If a preset is not available on the camera being used a preset of lower quality will be selected automatically. diff --git a/packages/camera/camera_platform_interface/test/utils/method_channel_mock.dart b/packages/camera/camera_platform_interface/test/utils/method_channel_mock.dart index 482ec6fcfbd7..cdf393f82b5f 100644 --- a/packages/camera/camera_platform_interface/test/utils/method_channel_mock.dart +++ b/packages/camera/camera_platform_interface/test/utils/method_channel_mock.dart @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + import 'package:flutter/services.dart'; class MethodChannelMock { From 987eba10096a9482dd517ac6a3ae0e2b72459736 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 30 Nov 2020 09:48:41 +0100 Subject: [PATCH 61/89] Fix formatting issues --- .../lib/src/method_channel/method_channel_camera.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 288bfb996238..0ef8708d60b8 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 @@ -211,7 +211,7 @@ class MethodChannelCamera extends CameraPlatform { } /// Converts messages received from the native platform into events. - /// + /// /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. @visibleForTesting From 2b2f2077d5b4f40206e58c99dffe7c6087de2ed6 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 30 Nov 2020 10:22:09 +0100 Subject: [PATCH 62/89] Updated CHANGELOG and version --- packages/camera/camera/CHANGELOG.md | 10 ++++++++++ packages/camera/camera/example/lib/main.dart | 2 +- packages/camera/camera/lib/camera.dart | 4 ++-- packages/camera/camera/pubspec.yaml | 2 +- .../lib/src/method_channel/method_channel_camera.dart | 2 +- .../lib/src/platform_interface/camera_platform.dart | 2 +- .../test/method_channel_camera_test.dart | 2 +- 7 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index c9f9dd0f8c58..687a76395cdf 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.6.0 + +As part of implementing federated architecture and making the interface compatible with the web this version contains the following **breaking changes**: + +Method changes in `CameraController`: +- The `takePicture` method no longer accepts the `path` parameter, but instead returns the captured image as an instance of the `XFile` class; +- The `startVideoRecording` method no longer accepts the `filePath`. Instead the recorded video is now returned as a `XFile` instance when the `stopVideoRecording` method completes; +- The `stopVideoRecording` method now returns the captured video when it completes; +- Added the `buildPreview` method which replaces the seperate `CameraPreview` widget. + ## 0.5.8+16 * Moved package to camera/camera subdir, to allow for federated implementations. diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index aa0c05d5eb33..0186d47d10e8 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -131,7 +131,7 @@ class _CameraExampleHomeState extends State } else { return AspectRatio( aspectRatio: controller.value.aspectRatio, - child: controller.buildView(), + child: controller.buildPreview(), ); } } diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index e313314ac36e..b0d7db9e5b02 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -450,7 +450,7 @@ class CameraController extends ValueNotifier { } /// Returns a widget showing a live camera preview. - Widget buildView() { + Widget buildPreview() { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController', @@ -458,7 +458,7 @@ class CameraController extends ValueNotifier { ); } try { - return CameraPlatform.instance.buildView(_cameraId); + return CameraPlatform.instance.buildPreview(_cameraId); } 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 092534e66554..b3e6efeea2a1 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.5.8+16 +version: 0.6.0 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera # TODO(mvanbeusekom): Remove when camera_platform_interface package is published. 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 0ef8708d60b8..a968d618761c 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 @@ -173,7 +173,7 @@ class MethodChannelCamera extends CameraPlatform { ); @override - Widget buildView(int cameraId) { + Widget buildPreview(int cameraId) { return Texture(textureId: 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 3d2b6fb0acf2..c192ebd7f454 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 @@ -110,7 +110,7 @@ abstract class CameraPlatform extends PlatformInterface { } /// Returns a widget showing a live camera preview. - Widget buildView(int cameraId) { + Widget buildPreview(int cameraId) { throw UnimplementedError('buildView() has not been implemented.'); } diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart index 009be3846088..8b36ef62860e 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart @@ -394,7 +394,7 @@ void main() { test('Should build a texture widget as view widget', () async { // Act - Widget widget = camera.buildView(cameraId); + Widget widget = camera.buildPreview(cameraId); // Act expect(widget is Texture, isTrue); From 1f9c343b6b00537723785457f359f9d2d5966b01 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Mon, 30 Nov 2020 16:39:12 +0100 Subject: [PATCH 63/89] Added more tests --- packages/camera/camera/test/camera_test.dart | 359 +++++++++++++++++- .../test/utils/method_channel_mock.dart | 38 ++ 2 files changed, 385 insertions(+), 12 deletions(-) create mode 100644 packages/camera/camera/test/utils/method_channel_mock.dart diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 66de0d9db5bf..f8c783fe8ad4 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -6,11 +6,14 @@ import 'dart:async'; import 'dart:ui'; import 'package:camera/camera.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'utils/method_channel_mock.dart'; + get mockAvailableCameras => [ CameraDescription( name: 'camBack', @@ -30,10 +33,12 @@ get mockOnCameraClosingEvent => null; get mockOnCameraErrorEvent => CameraErrorEvent(13, 'closing'); -get mockTakePicture => null; +XFile mockTakePicture = XFile('foo/bar.png'); get mockVideoRecordingXFile => null; +bool mockPlatformException = false; + void main() { WidgetsFlutterBinding.ensureInitialized(); group('camera', () { @@ -135,6 +140,335 @@ void main() { 'initialize was called on a disposed CameraController', ))); }); + + test('initialize() throws $CameraException on $PlatformException ', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + mockPlatformException = true; + + expect( + cameraController.initialize, + throwsA(isA().having( + (error) => error.description, + 'foo', + 'bar', + ))); + mockPlatformException = false; + }); + + test('prepareForVideoRecording() calls $CameraPlatform ', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.prepareForVideoRecording(); + + verify(CameraPlatform.instance.prepareForVideoRecording()).called(1); + }); + + test('takePicture() throws $CameraException when uninitialized ', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + expect( + cameraController.takePicture(), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController.', + 'takePicture was called on uninitialized CameraController', + ))); + }); + + test('takePicture() throws $CameraException when takePicture is true', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isTakingPicture: true); + expect( + cameraController.takePicture(), + throwsA(isA().having( + (error) => error.description, + 'Previous capture has not returned yet.', + 'takePicture was called before the previous capture returned.', + ))); + }); + + test('takePicture() returns $XFile', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + XFile xFile = await cameraController.takePicture(); + + expect(xFile.path, mockTakePicture.path); + }); + + test('takePicture() throws $CameraException on $PlatformException', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + mockPlatformException = true; + expect( + cameraController.takePicture(), + throwsA(isA().having( + (error) => error.description, + 'foo', + 'bar', + ))); + mockPlatformException = false; + }); + + test('startImageStream() throws $CameraException when uninitialized', () { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + expect( + cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController.', + 'startImageStream was called on uninitialized CameraController.', + ))); + }); + + test('startImageStream() throws $CameraException when recording videos', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + expect( + cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'startImageStream was called while a video is being recorded.', + ))); + }); + test( + 'startImageStream() throws $CameraException when already streaming images', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isStreamingImages: true); + expect( + cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'A camera has started streaming images.', + 'startImageStream was called while a camera was streaming images.', + ))); + }); + + + + test('startImageStream() calls CameraPlatform', () async { + MethodChannelMock methodChannelMock = + MethodChannelMock(channelName: 'plugins.flutter.io/camera', methods: { + 'startImageStream': {} + }); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.startImageStream((image) => null); + + expect(methodChannelMock.log, + [isMethodCall('startImageStream', arguments: null)]); + }); + + test('stopImageStream() throws $CameraException when uninitialized', () { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.stopImageStream(), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController.', + 'stopImageStream was called on uninitialized CameraController.', + ))); + }); + + test('stopImageStream() throws $CameraException when recording videos', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.startImageStream((image) => null); + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + expect( + cameraController.stopImageStream(), + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'stopImageStream was called while a video is being recorded.', + ))); + }); + + test('stopImageStream() throws $CameraException when not streaming images', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect( + cameraController.stopImageStream(), + throwsA(isA().having( + (error) => error.description, + 'No camera is streaming images', + 'stopImageStream was called when no camera is streaming images.', + ))); + }); + + + test('stopImageStream() intended behaviour', () async { + MethodChannelMock methodChannelMock = + MethodChannelMock(channelName: 'plugins.flutter.io/camera', methods: { + 'startImageStream': {}, + 'stopImageStream': {} + }); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + await cameraController.startImageStream((image) => null); + await cameraController.stopImageStream(); + expect(methodChannelMock.log, + [ + isMethodCall('startImageStream', arguments: null), + isMethodCall('stopImageStream', arguments: null) + ]); + }); + + test('startVideoRecording() throws $CameraException when uninitialized', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'startVideoRecording was called on uninitialized CameraController', + ))); + }); + test('startVideoRecording() throws $CameraException when recording videos', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'startVideoRecording was called when a recording is already started.', + ))); + }); + + test( + 'startVideoRecording() throws $CameraException when already streaming images', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isStreamingImages: true); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'A camera has started streaming images.', + 'startVideoRecording was called while a camera was streaming images.', + ))); + }); }); } @@ -151,25 +485,26 @@ class MockCameraPlatform extends Mock ResolutionPreset resolutionPreset, { bool enableAudio, }) => - Future.value(mockInitializeCamera); + mockPlatformException + ? throw PlatformException(code: 'foo', message: 'bar') + : Future.value(mockInitializeCamera); @override - Stream onCameraInitialized(int cameraId) { - return Stream.value(mockOnCameraInitializedEvent); - } + Stream onCameraInitialized(int cameraId) => + Stream.value(mockOnCameraInitializedEvent); @override - Stream onCameraClosing(int cameraId) { - return Stream.value(mockOnCameraClosingEvent); - } + Stream onCameraClosing(int cameraId) => + Stream.value(mockOnCameraClosingEvent); @override - Stream onCameraError(int cameraId) { - return Stream.value(mockOnCameraErrorEvent); - } + Stream onCameraError(int cameraId) => + Stream.value(mockOnCameraErrorEvent); @override - Future takePicture(int cameraId) => Future.value(mockTakePicture); + Future takePicture(int cameraId) => mockPlatformException + ? throw PlatformException(code: 'foo', message: 'bar') + : Future.value(mockTakePicture); @override Future startVideoRecording(int cameraId) => diff --git a/packages/camera/camera/test/utils/method_channel_mock.dart b/packages/camera/camera/test/utils/method_channel_mock.dart new file mode 100644 index 000000000000..cdf393f82b5f --- /dev/null +++ b/packages/camera/camera/test/utils/method_channel_mock.dart @@ -0,0 +1,38 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; + +class MethodChannelMock { + final Duration delay; + final MethodChannel methodChannel; + final Map methods; + final log = []; + + MethodChannelMock({ + String channelName, + this.delay, + this.methods, + }) : methodChannel = MethodChannel(channelName) { + methodChannel.setMockMethodCallHandler(_handler); + } + + Future _handler(MethodCall methodCall) async { + log.add(methodCall); + + if (!methods.containsKey(methodCall.method)) { + throw MissingPluginException('No implementation found for method ' + '${methodCall.method} on channel ${methodChannel.name}'); + } + + return Future.delayed(delay ?? Duration.zero, () { + final result = methods[methodCall.method]; + if (result is Exception) { + throw result; + } + + return Future.value(result); + }); + } +} From 1c8a1a103263beece8ace10956b90df389f24c8d Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Mon, 30 Nov 2020 16:44:00 +0100 Subject: [PATCH 64/89] Formatted code --- packages/camera/camera/test/camera_test.dart | 238 +++++++++---------- 1 file changed, 116 insertions(+), 122 deletions(-) diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index f8c783fe8ad4..24e0592bed8e 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -257,7 +257,7 @@ void main() { expect( cameraController.startImageStream((image) => null), throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController.', 'startImageStream was called on uninitialized CameraController.', ))); @@ -265,53 +265,50 @@ void main() { test('startImageStream() throws $CameraException when recording videos', () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isRecordingVideo: true); - expect( - cameraController.startImageStream((image) => null), - throwsA(isA().having( - (error) => error.description, - 'A video recording is already started.', - 'startImageStream was called while a video is being recorded.', - ))); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + expect( + cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'startImageStream was called while a video is being recorded.', + ))); }); test( 'startImageStream() throws $CameraException when already streaming images', () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isStreamingImages: true); - expect( - cameraController.startImageStream((image) => null), - throwsA(isA().having( - (error) => error.description, - 'A camera has started streaming images.', - 'startImageStream was called while a camera was streaming images.', - ))); - }); - + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + cameraController.value = + cameraController.value.copyWith(isStreamingImages: true); + expect( + cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'A camera has started streaming images.', + 'startImageStream was called while a camera was streaming images.', + ))); + }); test('startImageStream() calls CameraPlatform', () async { - MethodChannelMock methodChannelMock = - MethodChannelMock(channelName: 'plugins.flutter.io/camera', methods: { - 'startImageStream': {} - }); + MethodChannelMock methodChannelMock = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startImageStream': {}}); CameraController cameraController = CameraController( CameraDescription( name: 'cam', @@ -337,13 +334,14 @@ void main() { expect( cameraController.stopImageStream(), throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController.', 'stopImageStream was called on uninitialized CameraController.', ))); }); - test('stopImageStream() throws $CameraException when recording videos', () async { + test('stopImageStream() throws $CameraException when recording videos', + () async { CameraController cameraController = CameraController( CameraDescription( name: 'cam', @@ -358,7 +356,7 @@ void main() { expect( cameraController.stopImageStream(), throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'A video recording is already started.', 'stopImageStream was called while a video is being recorded.', ))); @@ -366,30 +364,27 @@ void main() { test('stopImageStream() throws $CameraException when not streaming images', () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - expect( - cameraController.stopImageStream(), - throwsA(isA().having( - (error) => error.description, - 'No camera is streaming images', - 'stopImageStream was called when no camera is streaming images.', - ))); - }); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + expect( + cameraController.stopImageStream(), + throwsA(isA().having( + (error) => error.description, + 'No camera is streaming images', + 'stopImageStream was called when no camera is streaming images.', + ))); + }); test('stopImageStream() intended behaviour', () async { - MethodChannelMock methodChannelMock = - MethodChannelMock(channelName: 'plugins.flutter.io/camera', methods: { - 'startImageStream': {}, - 'stopImageStream': {} - }); + MethodChannelMock methodChannelMock = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startImageStream': {}, 'stopImageStream': {}}); CameraController cameraController = CameraController( CameraDescription( name: 'cam', @@ -399,75 +394,74 @@ void main() { await cameraController.initialize(); await cameraController.startImageStream((image) => null); await cameraController.stopImageStream(); - expect(methodChannelMock.log, - [ - isMethodCall('startImageStream', arguments: null), - isMethodCall('stopImageStream', arguments: null) - ]); + expect(methodChannelMock.log, [ + isMethodCall('startImageStream', arguments: null), + isMethodCall('stopImageStream', arguments: null) + ]); }); test('startVideoRecording() throws $CameraException when uninitialized', () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - expect( - cameraController.startVideoRecording(), - throwsA(isA().having( - (error) => error.description, - 'Uninitialized CameraController', - 'startVideoRecording was called on uninitialized CameraController', - ))); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'startVideoRecording was called on uninitialized CameraController', + ))); }); test('startVideoRecording() throws $CameraException when recording videos', () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isRecordingVideo: true); - - expect( - cameraController.startVideoRecording(), - throwsA(isA().having( - (error) => error.description, - 'A video recording is already started.', - 'startVideoRecording was called when a recording is already started.', - ))); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'startVideoRecording was called when a recording is already started.', + ))); }); test( 'startVideoRecording() throws $CameraException when already streaming images', () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isStreamingImages: true); - - expect( - cameraController.startVideoRecording(), - throwsA(isA().having( - (error) => error.description, - 'A camera has started streaming images.', - 'startVideoRecording was called while a camera was streaming images.', - ))); + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isStreamingImages: true); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'A camera has started streaming images.', + 'startVideoRecording was called while a camera was streaming images.', + ))); }); }); } From cac52fc240726ca9c559b9f3b65ed3f071456e18 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 30 Nov 2020 18:24:31 +0100 Subject: [PATCH 65/89] Added additional unit-tests to platform_interface --- .../method_channel/method_channel_camera.dart | 14 +- .../test/events/camera_event_test.dart | 252 ++++++++++++++++++ .../method_channel_camera_test.dart | 121 ++++++++- .../test/types/camera_description_test.dart | 113 ++++++++ .../test/types/camera_exception_test.dart | 28 ++ .../test/types/resolution_preset_test.dart | 25 ++ 6 files changed, 545 insertions(+), 8 deletions(-) create mode 100644 packages/camera/camera_platform_interface/test/events/camera_event_test.dart rename packages/camera/camera_platform_interface/test/{ => method_channel}/method_channel_camera_test.dart (77%) create mode 100644 packages/camera/camera_platform_interface/test/types/camera_description_test.dart create mode 100644 packages/camera/camera_platform_interface/test/types/camera_exception_test.dart create mode 100644 packages/camera/camera_platform_interface/test/types/resolution_preset_test.dart 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 a968d618761c..13785fca36ce 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 @@ -28,11 +28,9 @@ class MethodChannelCamera extends CameraPlatform { final StreamController cameraEventStreamController = StreamController.broadcast(); - Stream _events(int cameraId) => cameraEventStreamController - .stream - .tap((event) => print( - 'Event received, EVENT TYPE: ${event.runtimeType}, STRING: ${event.toString()}')) - .where((event) => event.cameraId == cameraId); + Stream _events(int cameraId) => + cameraEventStreamController.stream + .where((event) => event.cameraId == cameraId); @override Future> availableCameras() async { @@ -106,7 +104,11 @@ class MethodChannelCamera extends CameraPlatform { 'dispose', {'cameraId': cameraId}, ); - _channels.remove(cameraId); + + if (_channels.containsKey(cameraId)) { + _channels[cameraId].setMethodCallHandler(null); + _channels.remove(cameraId); + } } @override diff --git a/packages/camera/camera_platform_interface/test/events/camera_event_test.dart b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart new file mode 100644 index 000000000000..01b03b8e93a0 --- /dev/null +++ b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart @@ -0,0 +1,252 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// 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'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('CameraInitializedEvent tests', () { + test('Constructor should initialize all properties', () { + final event = CameraInitializedEvent(1, 1024, 640); + + expect(event.cameraId, 1); + expect(event.previewWidth, 1024); + expect(event.previewHeight, 640); + }); + + test('fromJson should initialize all properties', () { + final event = CameraInitializedEvent.fromJson({ + 'cameraId': 1, + 'previewWidth': 1024.0, + 'previewHeight': 640.0, + }); + + expect(event.cameraId, 1); + expect(event.previewWidth, 1024); + expect(event.previewHeight, 640); + }); + + test('toJson should return a map with all fields', () { + final event = CameraInitializedEvent(1, 1024, 640); + + final jsonMap = event.toJson(); + + expect(jsonMap.length, 3); + expect(jsonMap['cameraId'], 1); + expect(jsonMap['previewWidth'], 1024); + expect(jsonMap['previewHeight'], 640); + }); + + test('equals should return true if objects are the same', () { + final firstEvent = CameraInitializedEvent(1, 1024, 640); + final secondEvent = CameraInitializedEvent(1, 1024, 640); + + expect(firstEvent == secondEvent, true); + }); + + test('equals should return false if cameraId is different', () { + final firstEvent = CameraInitializedEvent(1, 1024, 640); + final secondEvent = CameraInitializedEvent(2, 1024, 640); + + expect(firstEvent == secondEvent, false); + }); + + test('equals should return false if previewWidth is different', () { + final firstEvent = CameraInitializedEvent(1, 1024, 640); + final secondEvent = CameraInitializedEvent(1, 2048, 640); + + expect(firstEvent == secondEvent, false); + }); + + test('equals should return false if previewHeight is different', () { + final firstEvent = CameraInitializedEvent(1, 1024, 640); + final secondEvent = CameraInitializedEvent(1, 1024, 980); + + expect(firstEvent == secondEvent, false); + }); + + test('hashCode should match hashCode of all properties', () { + final event = CameraInitializedEvent(1, 1024, 640); + final expectedHashCode = event.cameraId.hashCode ^ + event.previewWidth.hashCode ^ + event.previewHeight.hashCode; + + expect(event.hashCode, expectedHashCode); + }); + }); + + group('CameraResolutionChangesEvent tests', () { + test('Constructor should initialize all properties', () { + final event = CameraResolutionChangedEvent(1, 1024, 640); + + expect(event.cameraId, 1); + expect(event.captureWidth, 1024); + expect(event.captureHeight, 640); + }); + + test('fromJson should initialize all properties', () { + final event = CameraResolutionChangedEvent.fromJson({ + 'cameraId': 1, + 'captureWidth': 1024.0, + 'captureHeight': 640.0, + }); + + expect(event.cameraId, 1); + expect(event.captureWidth, 1024); + expect(event.captureHeight, 640); + }); + + test('toJson should return a map with all fields', () { + final event = CameraResolutionChangedEvent(1, 1024, 640); + + final jsonMap = event.toJson(); + + expect(jsonMap.length, 3); + expect(jsonMap['cameraId'], 1); + expect(jsonMap['captureWidth'], 1024); + expect(jsonMap['captureHeight'], 640); + }); + + test('equals should return true if objects are the same', () { + final firstEvent = CameraResolutionChangedEvent(1, 1024, 640); + final secondEvent = CameraResolutionChangedEvent(1, 1024, 640); + + expect(firstEvent == secondEvent, true); + }); + + test('equals should return false if cameraId is different', () { + final firstEvent = CameraResolutionChangedEvent(1, 1024, 640); + final secondEvent = CameraResolutionChangedEvent(2, 1024, 640); + + expect(firstEvent == secondEvent, false); + }); + + test('equals should return false if captureWidth is different', () { + final firstEvent = CameraResolutionChangedEvent(1, 1024, 640); + final secondEvent = CameraResolutionChangedEvent(1, 2048, 640); + + expect(firstEvent == secondEvent, false); + }); + + test('equals should return false if captureHeight is different', () { + final firstEvent = CameraResolutionChangedEvent(1, 1024, 640); + final secondEvent = CameraResolutionChangedEvent(1, 1024, 980); + + expect(firstEvent == secondEvent, false); + }); + + test('hashCode should match hashCode of all properties', () { + final event = CameraResolutionChangedEvent(1, 1024, 640); + final expectedHashCode = event.cameraId.hashCode ^ + event.captureWidth.hashCode ^ + event.captureHeight.hashCode; + + expect(event.hashCode, expectedHashCode); + }); + }); + + group('CameraClosingEvent tests', () { + test('Constructor should initialize all properties', () { + final event = CameraClosingEvent(1); + + expect(event.cameraId, 1); + }); + + test('fromJson should initialize all properties', () { + final event = CameraClosingEvent.fromJson({ + 'cameraId': 1, + }); + + expect(event.cameraId, 1); + }); + + test('toJson should return a map with all fields', () { + final event = CameraClosingEvent(1); + + final jsonMap = event.toJson(); + + expect(jsonMap.length, 1); + expect(jsonMap['cameraId'], 1); + }); + + test('equals should return true if objects are the same', () { + final firstEvent = CameraClosingEvent(1); + final secondEvent = CameraClosingEvent(1); + + expect(firstEvent == secondEvent, true); + }); + + test('equals should return false if cameraId is different', () { + final firstEvent = CameraClosingEvent(1); + final secondEvent = CameraClosingEvent(2); + + expect(firstEvent == secondEvent, false); + }); + + test('hashCode should match hashCode of all properties', () { + final event = CameraClosingEvent(1); + final expectedHashCode = event.cameraId.hashCode; + + expect(event.hashCode, expectedHashCode); + }); + }); + + group('CameraErrorEvent tests', () { + test('Constructor should initialize all properties', () { + final event = CameraErrorEvent(1, 'Error'); + + expect(event.cameraId, 1); + expect(event.description, 'Error'); + }); + + test('fromJson should initialize all properties', () { + final event = CameraErrorEvent.fromJson( + {'cameraId': 1, 'description': 'Error'}); + + expect(event.cameraId, 1); + expect(event.description, 'Error'); + }); + + test('toJson should return a map with all fields', () { + final event = CameraErrorEvent(1, 'Error'); + + final jsonMap = event.toJson(); + + expect(jsonMap.length, 2); + expect(jsonMap['cameraId'], 1); + expect(jsonMap['description'], 'Error'); + }); + + test('equals should return true if objects are the same', () { + final firstEvent = CameraErrorEvent(1, 'Error'); + final secondEvent = CameraErrorEvent(1, 'Error'); + + expect(firstEvent == secondEvent, true); + }); + + test('equals should return false if cameraId is different', () { + final firstEvent = CameraErrorEvent(1, 'Error'); + final secondEvent = CameraErrorEvent(2, 'Error'); + + expect(firstEvent == secondEvent, false); + }); + + test('equals should return false if description is different', () { + final firstEvent = CameraErrorEvent(1, 'Error'); + final secondEvent = CameraErrorEvent(1, 'Ooops'); + + expect(firstEvent == secondEvent, false); + }); + + test('hashCode should match hashCode of all properties', () { + final event = CameraErrorEvent(1, 'Error'); + final expectedHashCode = + event.cameraId.hashCode ^ event.description.hashCode; + + expect(event.hashCode, expectedHashCode); + }); + }); +} diff --git a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart similarity index 77% rename from packages/camera/camera_platform_interface/test/method_channel_camera_test.dart rename to packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index 8b36ef62860e..b9d99f9c132c 100644 --- a/packages/camera/camera_platform_interface/test/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -11,7 +11,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'utils/method_channel_mock.dart'; +import '../utils/method_channel_mock.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -47,6 +47,60 @@ void main() { expect(cameraId, 1); }); + test( + 'Should throw CameraException when create throws a PlatformException', + () { + // Arrange + MethodChannelMock(channelName: 'plugins.flutter.io/camera', methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + final camera = MethodChannelCamera(); + + // Act + expect( + () => camera.createCamera( + CameraDescription(name: 'Test'), + ResolutionPreset.high, + ), + throwsA( + isA() + .having((e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); + + test( + 'Should throw CameraException when create throws a PlatformException', + () { + // Arrange + MethodChannelMock(channelName: 'plugins.flutter.io/camera', methods: { + 'create': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + final camera = MethodChannelCamera(); + + // Act + expect( + () => camera.createCamera( + CameraDescription(name: 'Test'), + ResolutionPreset.high, + ), + throwsA( + isA() + .having((e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); + test('Should send initialization data', () async { // Arrange MethodChannelMock cameraMockChannel = MethodChannelMock( @@ -285,6 +339,29 @@ void main() { } }); + test( + 'Should throw CameraException when availableCameras throws a PlatformException', + () { + // Arrange + MethodChannelMock(channelName: 'plugins.flutter.io/camera', methods: { + 'availableCameras': PlatformException( + code: 'TESTING_ERROR_CODE', + message: 'Mock error message used during testing.', + ) + }); + + // Act + expect( + camera.availableCameras, + throwsA( + isA() + .having((e) => e.code, 'code', 'TESTING_ERROR_CODE') + .having((e) => e.description, 'description', + 'Mock error message used during testing.'), + ), + ); + }); + test('Should take a picture and return an XFile instance', () async { // Arrange MethodChannelMock channel = MethodChannelMock( @@ -392,7 +469,7 @@ void main() { ]); }); - test('Should build a texture widget as view widget', () async { + test('Should build a texture widget as preview widget', () async { // Act Widget widget = camera.buildPreview(cameraId); @@ -401,5 +478,45 @@ void main() { expect((widget as Texture).textureId, cameraId); }); }); + + group('Utility methods', () { + test( + 'Should return CameraLensDirection when valid value is supplied when parsing camera lens direction', + () { + final camera = MethodChannelCamera(); + + expect( + camera.parseCameraLensDirection('back'), + CameraLensDirection.back, + ); + expect( + camera.parseCameraLensDirection('front'), + CameraLensDirection.front, + ); + expect( + camera.parseCameraLensDirection('external'), + CameraLensDirection.external, + ); + }); + + test( + 'Should throw ArgumentException when invalid value is supplied when parsing camera lens direction', + () { + final camera = MethodChannelCamera(); + + expect( + () => camera.parseCameraLensDirection('test'), + throwsA(isArgumentError), + ); + }); + + test('Should throw MissingPluginException when handling unknown method', + () { + final camera = MethodChannelCamera(); + + expect(() => camera.handleMethodCall(MethodCall('unknown_method'), 1), + throwsA(isA())); + }); + }); }); } diff --git a/packages/camera/camera_platform_interface/test/types/camera_description_test.dart b/packages/camera/camera_platform_interface/test/types/camera_description_test.dart new file mode 100644 index 000000000000..03909dbafb97 --- /dev/null +++ b/packages/camera/camera_platform_interface/test/types/camera_description_test.dart @@ -0,0 +1,113 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// 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'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('CameraLensDirection tests', () { + test('CameraLensDirection should contain 3 options', () { + final values = CameraLensDirection.values; + + expect(values.length, 3); + }); + + test("CameraLensDirection enum should have items in correct index", () { + final values = CameraLensDirection.values; + + expect(values[0], CameraLensDirection.front); + expect(values[1], CameraLensDirection.back); + expect(values[2], CameraLensDirection.external); + }); + }); + + group('CameraDescription tests', () { + test('Constructor should initialize all properties', () { + final description = CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.front, + sensorOrientation: 90, + ); + + expect(description.name, 'Test'); + expect(description.lensDirection, CameraLensDirection.front); + expect(description.sensorOrientation, 90); + }); + + test('equals should return true if objects are the same', () { + final firstDescription = CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.front, + sensorOrientation: 90, + ); + final secondDescription = CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.front, + sensorOrientation: 90, + ); + + expect(firstDescription == secondDescription, true); + }); + + test('equals should return false if name is different', () { + final firstDescription = CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.front, + sensorOrientation: 90, + ); + final secondDescription = CameraDescription( + name: 'Testing', + lensDirection: CameraLensDirection.front, + sensorOrientation: 90, + ); + + expect(firstDescription == secondDescription, false); + }); + + test('equals should return false if lens direction is different', () { + final firstDescription = CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.front, + sensorOrientation: 90, + ); + final secondDescription = CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90, + ); + + expect(firstDescription == secondDescription, false); + }); + + test('equals should return true if sensor orientation is different', () { + final firstDescription = CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.front, + sensorOrientation: 0, + ); + final secondDescription = CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.front, + sensorOrientation: 90, + ); + + expect(firstDescription == secondDescription, true); + }); + + test('hashCode should match hashCode of all properties', () { + final description = CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.front, + sensorOrientation: 0, + ); + final expectedHashCode = description.name.hashCode ^ + description.lensDirection.hashCode ^ + description.sensorOrientation.hashCode; + + expect(description.hashCode, expectedHashCode); + }); + }); +} diff --git a/packages/camera/camera_platform_interface/test/types/camera_exception_test.dart b/packages/camera/camera_platform_interface/test/types/camera_exception_test.dart new file mode 100644 index 000000000000..17370e4561f5 --- /dev/null +++ b/packages/camera/camera_platform_interface/test/types/camera_exception_test.dart @@ -0,0 +1,28 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// 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'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('constructor should initialize properties', () { + final code = 'TEST_ERROR'; + final description = 'This is a test error'; + final exception = CameraException(code, description); + + expect(exception.code, code); + expect(exception.description, description); + }); + + test('toString: Should return a description of the exception', () { + final code = 'TEST_ERROR'; + final description = 'This is a test error'; + final expected = 'CameraException($code, $description)'; + final exception = CameraException(code, description); + + final actual = exception.toString(); + + expect(actual, expected); + }); +} diff --git a/packages/camera/camera_platform_interface/test/types/resolution_preset_test.dart b/packages/camera/camera_platform_interface/test/types/resolution_preset_test.dart new file mode 100644 index 000000000000..aadf589f87c4 --- /dev/null +++ b/packages/camera/camera_platform_interface/test/types/resolution_preset_test.dart @@ -0,0 +1,25 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// 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'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('ResolutionPreset should contain 6 options', () { + final values = ResolutionPreset.values; + + expect(values.length, 6); + }); + + test("ResolutionPreset enum should have items in correct index", () { + final values = ResolutionPreset.values; + + expect(values[0], ResolutionPreset.low); + expect(values[1], ResolutionPreset.medium); + expect(values[2], ResolutionPreset.high); + expect(values[3], ResolutionPreset.veryHigh); + expect(values[4], ResolutionPreset.ultraHigh); + expect(values[5], ResolutionPreset.max); + }); +} From 3e991dd7fc543d48f73c7b22c131aaf2af400dca Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 30 Nov 2020 18:25:42 +0100 Subject: [PATCH 66/89] Fix formatting issues --- .../test/method_channel/method_channel_camera_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b9d99f9c132c..1dfa22b87f97 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 @@ -515,7 +515,7 @@ void main() { final camera = MethodChannelCamera(); expect(() => camera.handleMethodCall(MethodCall('unknown_method'), 1), - throwsA(isA())); + throwsA(isA())); }); }); }); From d613724573966994a44126c440a1133fff91cb9e Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 2 Dec 2020 14:37:11 +0100 Subject: [PATCH 67/89] Re-added the CameraPreview widget --- packages/camera/camera/example/lib/main.dart | 2 +- packages/camera/camera/lib/camera.dart | 474 +----------------- .../camera/lib/src/camera_controller.dart | 470 +++++++++++++++++ .../camera/lib/{ => src}/camera_image.dart | 2 +- .../camera/camera/lib/src/camera_preview.dart | 21 + 5 files changed, 500 insertions(+), 469 deletions(-) create mode 100644 packages/camera/camera/lib/src/camera_controller.dart rename packages/camera/camera/lib/{ => src}/camera_image.dart (99%) create mode 100644 packages/camera/camera/lib/src/camera_preview.dart diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 0186d47d10e8..e1edc1b06386 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -131,7 +131,7 @@ class _CameraExampleHomeState extends State } else { return AspectRatio( aspectRatio: controller.value.aspectRatio, - child: controller.buildPreview(), + child: CameraPreview(controller), ); } } diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index b0d7db9e5b02..bc513cef54a8 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -2,479 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +library camera; + import 'dart:async'; import 'dart:typed_data'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; import 'package:pedantic/pedantic.dart'; -export 'package:camera_platform_interface/camera_platform_interface.dart'; - -part 'camera_image.dart'; - -final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); - -/// Signature for a callback receiving the a camera image. -/// -/// This is used by [CameraController.startImageStream]. -// ignore: inference_failure_on_function_return_type -typedef onLatestImageAvailable = Function(CameraImage image); - -/// Completes with a list of available cameras. -/// -/// May throw a [CameraException]. -Future> availableCameras() async { - return CameraPlatform.instance.availableCameras(); -} - -/// The state of a [CameraController]. -class CameraValue { - /// Creates a new camera controller state. - const CameraValue({ - this.isInitialized, - this.errorDescription, - this.previewSize, - this.isRecordingVideo, - this.isTakingPicture, - this.isStreamingImages, - bool isRecordingPaused, - }) : _isRecordingPaused = isRecordingPaused; - - /// Creates a new camera controller state for an uninitialized controller. - const CameraValue.uninitialized() - : this( - isInitialized: false, - isRecordingVideo: false, - isTakingPicture: false, - isStreamingImages: false, - isRecordingPaused: false, - ); - - /// True after [CameraController.initialize] has completed successfully. - final bool isInitialized; - - /// True when a picture capture request has been sent but as not yet returned. - final bool isTakingPicture; - - /// True when the camera is recording (not the same as previewing). - final bool isRecordingVideo; - - /// True when images from the camera are being streamed. - final bool isStreamingImages; - - final bool _isRecordingPaused; - - /// True when camera [isRecordingVideo] and recording is paused. - bool get isRecordingPaused => isRecordingVideo && _isRecordingPaused; - - /// Description of an error state. - /// - /// This is null while the controller is not in an error state. - /// When [hasError] is true this contains the error description. - final String errorDescription; - - /// The size of the preview in pixels. - /// - /// Is `null` until [isInitialized] is `true`. - final Size previewSize; - - /// Convenience getter for `previewSize.height / previewSize.width`. - /// - /// Can only be called when [initialize] is done. - double get aspectRatio => previewSize.height / previewSize.width; - - /// Whether the controller is in an error state. - /// - /// When true [errorDescription] describes the error. - bool get hasError => errorDescription != null; - - /// Creates a modified copy of the object. - /// - /// Explicitly specified fields get the specified value, all other fields get - /// the same value of the current object. - CameraValue copyWith({ - bool isInitialized, - bool isRecordingVideo, - bool isTakingPicture, - bool isStreamingImages, - String errorDescription, - Size previewSize, - bool isRecordingPaused, - }) { - return CameraValue( - isInitialized: isInitialized ?? this.isInitialized, - errorDescription: errorDescription, - previewSize: previewSize ?? this.previewSize, - isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo, - isTakingPicture: isTakingPicture ?? this.isTakingPicture, - isStreamingImages: isStreamingImages ?? this.isStreamingImages, - isRecordingPaused: isRecordingPaused ?? _isRecordingPaused, - ); - } - - @override - String toString() { - return '$runtimeType(' - 'isRecordingVideo: $isRecordingVideo, ' - 'isInitialized: $isInitialized, ' - 'errorDescription: $errorDescription, ' - 'previewSize: $previewSize, ' - 'isStreamingImages: $isStreamingImages)'; - } -} - -/// Controls a device camera. -/// -/// Use [availableCameras] to get a list of available cameras. -/// -/// Before using a [CameraController] a call to [initialize] must complete. -/// -/// To show the camera preview on the screen use a [CameraPreview] widget. -class CameraController extends ValueNotifier { - /// Creates a new camera controller in an uninitialized state. - CameraController( - this.description, - this.resolutionPreset, { - this.enableAudio = true, - }) : super(const CameraValue.uninitialized()); - - /// The properties of the camera device controlled by this controller. - final CameraDescription description; - /// The resolution this controller is targeting. - /// - /// This resolution preset is not guaranteed to be available on the device, - /// if unavailable a lower resolution will be used. - /// - /// See also: [ResolutionPreset]. - final ResolutionPreset resolutionPreset; - - /// Whether to include audio when recording a video. - final bool enableAudio; - - int _cameraId; - bool _isDisposed = false; - StreamSubscription _imageStreamSubscription; - Completer _creatingCompleter; - - /// Checks whether [CameraController.dispose] has completed successfully. - /// - /// This is a no-op when asserts are disabled. - void debugCheckIsDisposed() { - assert(_isDisposed); - } - - /// Initializes the camera on the device. - /// - /// Throws a [CameraException] if the initialization fails. - Future initialize() async { - if (_isDisposed) { - throw CameraException( - 'Disposed CameraController', - 'initialize was called on a disposed CameraController', - ); - } - try { - _creatingCompleter = Completer(); - Completer _resolutionCompleter = Completer(); - - _cameraId = await CameraPlatform.instance.createCamera( - description, - resolutionPreset, - enableAudio: enableAudio, - ); - - unawaited( - CameraPlatform.instance - .onCameraInitialized(_cameraId) - .map((event) { - return Size( - event.previewWidth, - event.previewHeight, - ); - }) - .first - .then((previewSize) => _resolutionCompleter.complete(previewSize)), - ); - - await CameraPlatform.instance.initializeCamera(_cameraId); - - value = value.copyWith( - isInitialized: true, - previewSize: await _resolutionCompleter.future, - ); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - _creatingCompleter.complete(); - return _creatingCompleter.future; - } - - /// Prepare the capture session for video recording. - /// - /// Use of this method is optional, but it may be called for performance - /// reasons on iOS. - /// - /// Preparing audio can cause a minor delay in the CameraPreview view on iOS. - /// If video recording is intended, calling this early eliminates this delay - /// that would otherwise be experienced when video recording is started. - /// This operation is a no-op on Android. - /// - /// Throws a [CameraException] if the prepare fails. - Future prepareForVideoRecording() async { - await CameraPlatform.instance.prepareForVideoRecording(); - } - - /// Captures an image and saves it to [path]. - /// - /// A path can for example be obtained using - /// [path_provider](https://pub.dartlang.org/packages/path_provider). - /// - /// If a file already exists at the provided path an error will be thrown. - /// The file can be read as this function returns. - /// - /// Throws a [CameraException] if the capture fails. - Future takePicture() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController.', - 'takePicture was called on uninitialized CameraController', - ); - } - if (value.isTakingPicture) { - throw CameraException( - 'Previous capture has not returned yet.', - 'takePicture was called before the previous capture returned.', - ); - } - try { - value = value.copyWith(isTakingPicture: true); - XFile file = await CameraPlatform.instance.takePicture(_cameraId); - value = value.copyWith(isTakingPicture: false); - return file; - } on PlatformException catch (e) { - value = value.copyWith(isTakingPicture: false); - throw CameraException(e.code, e.message); - } - } - - /// Start streaming images from platform camera. - /// - /// Settings for capturing images on iOS and Android is set to always use the - /// latest image available from the camera and will drop all other images. - /// - /// When running continuously with [CameraPreview] widget, this function runs - /// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can - /// have significant frame rate drops for [CameraPreview] on lower end - /// devices. - /// - /// Throws a [CameraException] if image streaming or video recording has - /// already started. - // TODO(bmparr): Add settings for resolution and fps. - Future startImageStream(onLatestImageAvailable onAvailable) async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'startImageStream was called on uninitialized CameraController.', - ); - } - if (value.isRecordingVideo) { - throw CameraException( - 'A video recording is already started.', - 'startImageStream was called while a video is being recorded.', - ); - } - if (value.isStreamingImages) { - throw CameraException( - 'A camera has started streaming images.', - 'startImageStream was called while a camera was streaming images.', - ); - } - - try { - await _channel.invokeMethod('startImageStream'); - value = value.copyWith(isStreamingImages: true); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - const EventChannel cameraEventChannel = - EventChannel('plugins.flutter.io/camera/imageStream'); - _imageStreamSubscription = - cameraEventChannel.receiveBroadcastStream().listen( - (dynamic imageData) { - onAvailable(CameraImage.fromPlatformData(imageData)); - }, - ); - } - - /// Stop streaming images from platform camera. - /// - /// Throws a [CameraException] if image streaming was not started or video - /// recording was started. - Future stopImageStream() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'stopImageStream was called on uninitialized CameraController.', - ); - } - if (value.isRecordingVideo) { - throw CameraException( - 'A video recording is already started.', - 'stopImageStream was called while a video is being recorded.', - ); - } - if (!value.isStreamingImages) { - throw CameraException( - 'No camera is streaming images', - 'stopImageStream was called when no camera is streaming images.', - ); - } - - try { - value = value.copyWith(isStreamingImages: false); - await _channel.invokeMethod('stopImageStream'); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - - await _imageStreamSubscription.cancel(); - _imageStreamSubscription = null; - } - - /// Start a video recording. - /// - /// The video is returned as a [XFile] after calling [stopVideoRecording]. - /// Throws a [CameraException] if the capture fails. - Future startVideoRecording() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'startVideoRecording was called on uninitialized CameraController', - ); - } - if (value.isRecordingVideo) { - throw CameraException( - 'A video recording is already started.', - 'startVideoRecording was called when a recording is already started.', - ); - } - if (value.isStreamingImages) { - throw CameraException( - 'A camera has started streaming images.', - 'startVideoRecording was called while a camera was streaming images.', - ); - } - - try { - await CameraPlatform.instance.startVideoRecording(_cameraId); - value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } - - /// Stops the video recording and returns the file where it was saved. - /// - /// Throws a [CameraException] if the capture failed. - Future stopVideoRecording() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'stopVideoRecording was called on uninitialized CameraController', - ); - } - if (!value.isRecordingVideo) { - throw CameraException( - 'No video is recording', - 'stopVideoRecording was called when no video is recording.', - ); - } - try { - XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); - value = value.copyWith(isRecordingVideo: false); - return file; - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } - - /// Pause video recording. - /// - /// This feature is only available on iOS and Android sdk 24+. - Future pauseVideoRecording() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'pauseVideoRecording was called on uninitialized CameraController', - ); - } - if (!value.isRecordingVideo) { - throw CameraException( - 'No video is recording', - 'pauseVideoRecording was called when no video is recording.', - ); - } - try { - await CameraPlatform.instance.pauseVideoRecording(_cameraId); - value = value.copyWith(isRecordingPaused: true); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } - - /// Resume video recording after pausing. - /// - /// This feature is only available on iOS and Android sdk 24+. - Future resumeVideoRecording() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'resumeVideoRecording was called on uninitialized CameraController', - ); - } - if (!value.isRecordingVideo) { - throw CameraException( - 'No video is recording', - 'resumeVideoRecording was called when no video is recording.', - ); - } - try { - await CameraPlatform.instance.resumeVideoRecording(_cameraId); - value = value.copyWith(isRecordingPaused: false); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } - - /// Returns a widget showing a live camera preview. - Widget buildPreview() { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'buildView() was called on uninitialized CameraController.', - ); - } - try { - return CameraPlatform.instance.buildPreview(_cameraId); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } +export 'package:camera_platform_interface/camera_platform_interface.dart'; - /// Releases the resources of this camera. - @override - Future dispose() async { - if (_isDisposed) { - return; - } - _isDisposed = true; - super.dispose(); - if (_creatingCompleter != null) { - await _creatingCompleter.future; - await CameraPlatform.instance.dispose(_cameraId); - } - } -} +part 'src/camera_controller.dart'; +part 'src/camera_image.dart'; +part 'src/camera_preview.dart'; diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart new file mode 100644 index 000000000000..d41fe512ae45 --- /dev/null +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -0,0 +1,470 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of camera; + +final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); + +/// Signature for a callback receiving the a camera image. +/// +/// This is used by [CameraController.startImageStream]. +// ignore: inference_failure_on_function_return_type +typedef onLatestImageAvailable = Function(CameraImage image); + +/// Completes with a list of available cameras. +/// +/// May throw a [CameraException]. +Future> availableCameras() async { + return CameraPlatform.instance.availableCameras(); +} + +/// The state of a [CameraController]. +class CameraValue { + /// Creates a new camera controller state. + const CameraValue({ + this.isInitialized, + this.errorDescription, + this.previewSize, + this.isRecordingVideo, + this.isTakingPicture, + this.isStreamingImages, + bool isRecordingPaused, + }) : _isRecordingPaused = isRecordingPaused; + + /// Creates a new camera controller state for an uninitialized controller. + const CameraValue.uninitialized() + : this( + isInitialized: false, + isRecordingVideo: false, + isTakingPicture: false, + isStreamingImages: false, + isRecordingPaused: false, + ); + + /// True after [CameraController.initialize] has completed successfully. + final bool isInitialized; + + /// True when a picture capture request has been sent but as not yet returned. + final bool isTakingPicture; + + /// True when the camera is recording (not the same as previewing). + final bool isRecordingVideo; + + /// True when images from the camera are being streamed. + final bool isStreamingImages; + + final bool _isRecordingPaused; + + /// True when camera [isRecordingVideo] and recording is paused. + bool get isRecordingPaused => isRecordingVideo && _isRecordingPaused; + + /// Description of an error state. + /// + /// This is null while the controller is not in an error state. + /// When [hasError] is true this contains the error description. + final String errorDescription; + + /// The size of the preview in pixels. + /// + /// Is `null` until [isInitialized] is `true`. + final Size previewSize; + + /// Convenience getter for `previewSize.height / previewSize.width`. + /// + /// Can only be called when [initialize] is done. + double get aspectRatio => previewSize.height / previewSize.width; + + /// Whether the controller is in an error state. + /// + /// When true [errorDescription] describes the error. + bool get hasError => errorDescription != null; + + /// Creates a modified copy of the object. + /// + /// Explicitly specified fields get the specified value, all other fields get + /// the same value of the current object. + CameraValue copyWith({ + bool isInitialized, + bool isRecordingVideo, + bool isTakingPicture, + bool isStreamingImages, + String errorDescription, + Size previewSize, + bool isRecordingPaused, + }) { + return CameraValue( + isInitialized: isInitialized ?? this.isInitialized, + errorDescription: errorDescription, + previewSize: previewSize ?? this.previewSize, + isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo, + isTakingPicture: isTakingPicture ?? this.isTakingPicture, + isStreamingImages: isStreamingImages ?? this.isStreamingImages, + isRecordingPaused: isRecordingPaused ?? _isRecordingPaused, + ); + } + + @override + String toString() { + return '$runtimeType(' + 'isRecordingVideo: $isRecordingVideo, ' + 'isInitialized: $isInitialized, ' + 'errorDescription: $errorDescription, ' + 'previewSize: $previewSize, ' + 'isStreamingImages: $isStreamingImages)'; + } +} + +/// Controls a device camera. +/// +/// Use [availableCameras] to get a list of available cameras. +/// +/// Before using a [CameraController] a call to [initialize] must complete. +/// +/// To show the camera preview on the screen use a [CameraPreview] widget. +class CameraController extends ValueNotifier { + /// Creates a new camera controller in an uninitialized state. + CameraController( + this.description, + this.resolutionPreset, { + this.enableAudio = true, + }) : super(const CameraValue.uninitialized()); + + /// The properties of the camera device controlled by this controller. + final CameraDescription description; + + /// The resolution this controller is targeting. + /// + /// This resolution preset is not guaranteed to be available on the device, + /// if unavailable a lower resolution will be used. + /// + /// See also: [ResolutionPreset]. + final ResolutionPreset resolutionPreset; + + /// Whether to include audio when recording a video. + final bool enableAudio; + + int _cameraId; + bool _isDisposed = false; + StreamSubscription _imageStreamSubscription; + Completer _creatingCompleter; + + /// Checks whether [CameraController.dispose] has completed successfully. + /// + /// This is a no-op when asserts are disabled. + void debugCheckIsDisposed() { + assert(_isDisposed); + } + + /// Initializes the camera on the device. + /// + /// Throws a [CameraException] if the initialization fails. + Future initialize() async { + if (_isDisposed) { + throw CameraException( + 'Disposed CameraController', + 'initialize was called on a disposed CameraController', + ); + } + try { + _creatingCompleter = Completer(); + Completer _resolutionCompleter = Completer(); + + _cameraId = await CameraPlatform.instance.createCamera( + description, + resolutionPreset, + enableAudio: enableAudio, + ); + + unawaited( + CameraPlatform.instance + .onCameraInitialized(_cameraId) + .map((event) { + return Size( + event.previewWidth, + event.previewHeight, + ); + }) + .first + .then((previewSize) => _resolutionCompleter.complete(previewSize)), + ); + + await CameraPlatform.instance.initializeCamera(_cameraId); + + value = value.copyWith( + isInitialized: true, + previewSize: await _resolutionCompleter.future, + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + _creatingCompleter.complete(); + return _creatingCompleter.future; + } + + /// Prepare the capture session for video recording. + /// + /// Use of this method is optional, but it may be called for performance + /// reasons on iOS. + /// + /// Preparing audio can cause a minor delay in the CameraPreview view on iOS. + /// If video recording is intended, calling this early eliminates this delay + /// that would otherwise be experienced when video recording is started. + /// This operation is a no-op on Android. + /// + /// Throws a [CameraException] if the prepare fails. + Future prepareForVideoRecording() async { + await CameraPlatform.instance.prepareForVideoRecording(); + } + + /// Captures an image and saves it to [path]. + /// + /// A path can for example be obtained using + /// [path_provider](https://pub.dartlang.org/packages/path_provider). + /// + /// If a file already exists at the provided path an error will be thrown. + /// The file can be read as this function returns. + /// + /// Throws a [CameraException] if the capture fails. + Future takePicture() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController.', + 'takePicture was called on uninitialized CameraController', + ); + } + if (value.isTakingPicture) { + throw CameraException( + 'Previous capture has not returned yet.', + 'takePicture was called before the previous capture returned.', + ); + } + try { + value = value.copyWith(isTakingPicture: true); + XFile file = await CameraPlatform.instance.takePicture(_cameraId); + value = value.copyWith(isTakingPicture: false); + return file; + } on PlatformException catch (e) { + value = value.copyWith(isTakingPicture: false); + throw CameraException(e.code, e.message); + } + } + + /// Start streaming images from platform camera. + /// + /// Settings for capturing images on iOS and Android is set to always use the + /// latest image available from the camera and will drop all other images. + /// + /// When running continuously with [CameraPreview] widget, this function runs + /// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can + /// have significant frame rate drops for [CameraPreview] on lower end + /// devices. + /// + /// Throws a [CameraException] if image streaming or video recording has + /// already started. + // TODO(bmparr): Add settings for resolution and fps. + Future startImageStream(onLatestImageAvailable onAvailable) async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'startImageStream was called on uninitialized CameraController.', + ); + } + if (value.isRecordingVideo) { + throw CameraException( + 'A video recording is already started.', + 'startImageStream was called while a video is being recorded.', + ); + } + if (value.isStreamingImages) { + throw CameraException( + 'A camera has started streaming images.', + 'startImageStream was called while a camera was streaming images.', + ); + } + + try { + await _channel.invokeMethod('startImageStream'); + value = value.copyWith(isStreamingImages: true); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + const EventChannel cameraEventChannel = + EventChannel('plugins.flutter.io/camera/imageStream'); + _imageStreamSubscription = + cameraEventChannel.receiveBroadcastStream().listen( + (dynamic imageData) { + onAvailable(CameraImage.fromPlatformData(imageData)); + }, + ); + } + + /// Stop streaming images from platform camera. + /// + /// Throws a [CameraException] if image streaming was not started or video + /// recording was started. + Future stopImageStream() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'stopImageStream was called on uninitialized CameraController.', + ); + } + if (value.isRecordingVideo) { + throw CameraException( + 'A video recording is already started.', + 'stopImageStream was called while a video is being recorded.', + ); + } + if (!value.isStreamingImages) { + throw CameraException( + 'No camera is streaming images', + 'stopImageStream was called when no camera is streaming images.', + ); + } + + try { + value = value.copyWith(isStreamingImages: false); + await _channel.invokeMethod('stopImageStream'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + + await _imageStreamSubscription.cancel(); + _imageStreamSubscription = null; + } + + /// Start a video recording. + /// + /// The video is returned as a [XFile] after calling [stopVideoRecording]. + /// Throws a [CameraException] if the capture fails. + Future startVideoRecording() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'startVideoRecording was called on uninitialized CameraController', + ); + } + if (value.isRecordingVideo) { + throw CameraException( + 'A video recording is already started.', + 'startVideoRecording was called when a recording is already started.', + ); + } + if (value.isStreamingImages) { + throw CameraException( + 'A camera has started streaming images.', + 'startVideoRecording was called while a camera was streaming images.', + ); + } + + try { + await CameraPlatform.instance.startVideoRecording(_cameraId); + value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Stops the video recording and returns the file where it was saved. + /// + /// Throws a [CameraException] if the capture failed. + Future stopVideoRecording() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'stopVideoRecording was called on uninitialized CameraController', + ); + } + if (!value.isRecordingVideo) { + throw CameraException( + 'No video is recording', + 'stopVideoRecording was called when no video is recording.', + ); + } + try { + XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); + value = value.copyWith(isRecordingVideo: false); + return file; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Pause video recording. + /// + /// This feature is only available on iOS and Android sdk 24+. + Future pauseVideoRecording() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'pauseVideoRecording was called on uninitialized CameraController', + ); + } + if (!value.isRecordingVideo) { + throw CameraException( + 'No video is recording', + 'pauseVideoRecording was called when no video is recording.', + ); + } + try { + await CameraPlatform.instance.pauseVideoRecording(_cameraId); + value = value.copyWith(isRecordingPaused: true); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Resume video recording after pausing. + /// + /// This feature is only available on iOS and Android sdk 24+. + Future resumeVideoRecording() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'resumeVideoRecording was called on uninitialized CameraController', + ); + } + if (!value.isRecordingVideo) { + throw CameraException( + 'No video is recording', + 'resumeVideoRecording was called when no video is recording.', + ); + } + try { + await CameraPlatform.instance.resumeVideoRecording(_cameraId); + value = value.copyWith(isRecordingPaused: false); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Returns a widget showing a live camera preview. + Widget buildPreview() { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'buildView() was called on uninitialized CameraController.', + ); + } + try { + return CameraPlatform.instance.buildPreview(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Releases the resources of this camera. + @override + Future dispose() async { + if (_isDisposed) { + return; + } + _isDisposed = true; + super.dispose(); + if (_creatingCompleter != null) { + await _creatingCompleter.future; + await CameraPlatform.instance.dispose(_cameraId); + } + } +} diff --git a/packages/camera/camera/lib/camera_image.dart b/packages/camera/camera/lib/src/camera_image.dart similarity index 99% rename from packages/camera/camera/lib/camera_image.dart rename to packages/camera/camera/lib/src/camera_image.dart index 0b84b19be6e8..ebeb3e1c4bdd 100644 --- a/packages/camera/camera/lib/camera_image.dart +++ b/packages/camera/camera/lib/src/camera_image.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of 'camera.dart'; +part of camera; /// A single color plane of image data. /// diff --git a/packages/camera/camera/lib/src/camera_preview.dart b/packages/camera/camera/lib/src/camera_preview.dart new file mode 100644 index 000000000000..8a79c7ee8c50 --- /dev/null +++ b/packages/camera/camera/lib/src/camera_preview.dart @@ -0,0 +1,21 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of camera; + +/// A widget showing a live camera preview. +class CameraPreview extends StatelessWidget { + /// Creates a preview widget for the given camera controller. + const CameraPreview(this.controller); + + /// The controller for the camera that the preview is shown for. + final CameraController controller; + + @override + Widget build(BuildContext context) { + return controller.value.isInitialized + ? Texture(textureId: controller._cameraId) + : Container(); + } +} From 343294a30dffbd434e3f2c72e159efac183236aa Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 3 Dec 2020 10:09:04 +0100 Subject: [PATCH 68/89] Refactored CameraException not to use --- .../lib/src/types/camera_exception.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_platform_interface/lib/src/types/camera_exception.dart b/packages/camera/camera_platform_interface/lib/src/types/camera_exception.dart index 5734f3e11612..3da659f7021d 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/camera_exception.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/camera_exception.dart @@ -16,5 +16,5 @@ class CameraException implements Exception { String description; @override - String toString() => '$runtimeType($code, $description)'; + String toString() => 'CameraException($code, $description)'; } From f385a29456083a9bf45ba4042041e29ac05f13de Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 3 Dec 2020 11:39:00 +0100 Subject: [PATCH 69/89] Use import/export instead of part implementation --- packages/camera/camera/lib/camera.dart | 17 ++++------------- .../camera/lib/src/camera_controller.dart | 12 +++++++++++- .../camera/camera/lib/src/camera_image.dart | 5 ++++- .../camera/camera/lib/src/camera_preview.dart | 6 ++++-- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index bc513cef54a8..bba560470138 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -2,19 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -library camera; - -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:pedantic/pedantic.dart'; +export 'src/camera_controller.dart'; +export 'src/camera_image.dart'; +export 'src/camera_preview.dart'; export 'package:camera_platform_interface/camera_platform_interface.dart'; -part 'src/camera_controller.dart'; -part 'src/camera_image.dart'; -part 'src/camera_preview.dart'; + diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index d41fe512ae45..ad8a527c63eb 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -2,7 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of camera; +import 'dart:async'; + +import 'package:camera/camera.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:pedantic/pedantic.dart'; final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); @@ -156,6 +163,9 @@ class CameraController extends ValueNotifier { assert(_isDisposed); } + /// The camera identifier with which the controller is associated. + int get cameraId => _cameraId; + /// Initializes the camera on the device. /// /// Throws a [CameraException] if the initialization fails. diff --git a/packages/camera/camera/lib/src/camera_image.dart b/packages/camera/camera/lib/src/camera_image.dart index ebeb3e1c4bdd..ca8115eb758d 100644 --- a/packages/camera/camera/lib/src/camera_image.dart +++ b/packages/camera/camera/lib/src/camera_image.dart @@ -2,7 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of camera; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; /// A single color plane of image data. /// diff --git a/packages/camera/camera/lib/src/camera_preview.dart b/packages/camera/camera/lib/src/camera_preview.dart index 8a79c7ee8c50..bf7862eb9151 100644 --- a/packages/camera/camera/lib/src/camera_preview.dart +++ b/packages/camera/camera/lib/src/camera_preview.dart @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of camera; +import 'package:camera/camera.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/material.dart'; /// A widget showing a live camera preview. class CameraPreview extends StatelessWidget { @@ -15,7 +17,7 @@ class CameraPreview extends StatelessWidget { @override Widget build(BuildContext context) { return controller.value.isInitialized - ? Texture(textureId: controller._cameraId) + ? CameraPlatform.instance.buildPreview(controller.cameraId) : Container(); } } From 557d056e213f992cbba5a4640cfe129857ed68a4 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 3 Dec 2020 13:08:44 +0100 Subject: [PATCH 70/89] fixed formatting --- packages/camera/camera/lib/camera.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index bba560470138..7135764d2eb5 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -7,5 +7,3 @@ export 'src/camera_image.dart'; export 'src/camera_preview.dart'; export 'package:camera_platform_interface/camera_platform_interface.dart'; - - From ab30e1d655cb27a5f8b6fae5ebaec755c85a0c52 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 3 Dec 2020 13:47:53 +0100 Subject: [PATCH 71/89] Resolved additional feedback --- packages/camera/camera/CHANGELOG.md | 2 +- .../lib/src/events/camera_event.dart | 2 +- .../method_channel/method_channel_camera.dart | 24 ++++--------- .../method_channel_camera_test.dart | 35 ++----------------- 4 files changed, 10 insertions(+), 53 deletions(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 687a76395cdf..9122a2b2d2cf 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -6,7 +6,7 @@ Method changes in `CameraController`: - The `takePicture` method no longer accepts the `path` parameter, but instead returns the captured image as an instance of the `XFile` class; - The `startVideoRecording` method no longer accepts the `filePath`. Instead the recorded video is now returned as a `XFile` instance when the `stopVideoRecording` method completes; - The `stopVideoRecording` method now returns the captured video when it completes; -- Added the `buildPreview` method which replaces the seperate `CameraPreview` widget. +- Added the `buildPreview` method which is now used to implement the CameraPreview widget. ## 0.5.8+16 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 70158a720ac8..ab3d45545f23 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 @@ -24,7 +24,7 @@ abstract class CameraEvent { /// Build a Camera Event, that relates a `cameraId`. /// /// The `cameraId` is the ID of the camera that triggered the event. - CameraEvent(this.cameraId); + CameraEvent(this.cameraId) : assert(cameraId != null); @override bool operator ==(Object other) => 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 13785fca36ce..c649a41f5e9f 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 @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/utils/utils.dart'; import 'package:cross_file/cross_file.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -75,12 +76,12 @@ class MethodChannelCamera extends CameraPlatform { @override Future initializeCamera(int cameraId) { - if (!_channels.containsKey(cameraId)) { + _channels.putIfAbsent(cameraId, () { final channel = MethodChannel('flutter.io/cameraPlugin/camera$cameraId'); channel.setMethodCallHandler( (MethodCall call) => handleMethodCall(call, cameraId)); - _channels[cameraId] = channel; - } + return channel; + }); Completer _completer = Completer(); @@ -194,22 +195,9 @@ class MethodChannelCamera extends CameraPlatform { return 'medium'; case ResolutionPreset.low: return 'low'; + default: + throw ArgumentError('Unknown ResolutionPreset value'); } - throw ArgumentError('Unknown ResolutionPreset value'); - } - - /// Parses a string into a corresponding CameraLensDirection. - @visibleForTesting - CameraLensDirection parseCameraLensDirection(String string) { - switch (string) { - case 'front': - return CameraLensDirection.front; - case 'back': - return CameraLensDirection.back; - case 'external': - return CameraLensDirection.external; - } - throw ArgumentError('Unknown CameraLensDirection value'); } /// Converts messages received from the native platform into events. 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 1dfa22b87f97..952a78e3408b 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 @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; +import 'package:camera_platform_interface/src/utils/utils.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -332,7 +333,7 @@ void main() { CameraDescription cameraDescription = CameraDescription( name: returnData[i]['name'], lensDirection: - camera.parseCameraLensDirection(returnData[i]['lensFacing']), + parseCameraLensDirection(returnData[i]['lensFacing']), sensorOrientation: returnData[i]['sensorOrientation'], ); expect(cameras[i], cameraDescription); @@ -477,38 +478,6 @@ void main() { expect(widget is Texture, isTrue); expect((widget as Texture).textureId, cameraId); }); - }); - - group('Utility methods', () { - test( - 'Should return CameraLensDirection when valid value is supplied when parsing camera lens direction', - () { - final camera = MethodChannelCamera(); - - expect( - camera.parseCameraLensDirection('back'), - CameraLensDirection.back, - ); - expect( - camera.parseCameraLensDirection('front'), - CameraLensDirection.front, - ); - expect( - camera.parseCameraLensDirection('external'), - CameraLensDirection.external, - ); - }); - - test( - 'Should throw ArgumentException when invalid value is supplied when parsing camera lens direction', - () { - final camera = MethodChannelCamera(); - - expect( - () => camera.parseCameraLensDirection('test'), - throwsA(isArgumentError), - ); - }); test('Should throw MissingPluginException when handling unknown method', () { From 22ec1d7409df76262b163bba4164959b645d0ca4 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 3 Dec 2020 13:48:11 +0100 Subject: [PATCH 72/89] Resolved additional feedback --- .../lib/src/utils/utils.dart | 14 ++++++++ .../test/utils/utils_test.dart | 33 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 packages/camera/camera_platform_interface/lib/src/utils/utils.dart create mode 100644 packages/camera/camera_platform_interface/test/utils/utils_test.dart diff --git a/packages/camera/camera_platform_interface/lib/src/utils/utils.dart b/packages/camera/camera_platform_interface/lib/src/utils/utils.dart new file mode 100644 index 000000000000..f94d8e69c07e --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/utils/utils.dart @@ -0,0 +1,14 @@ +import 'package:camera_platform_interface/camera_platform_interface.dart'; + +/// Parses a string into a corresponding CameraLensDirection. +CameraLensDirection parseCameraLensDirection(String string) { + switch (string) { + case 'front': + return CameraLensDirection.front; + case 'back': + return CameraLensDirection.back; + case 'external': + return CameraLensDirection.external; + } + throw ArgumentError('Unknown CameraLensDirection value'); +} diff --git a/packages/camera/camera_platform_interface/test/utils/utils_test.dart b/packages/camera/camera_platform_interface/test/utils/utils_test.dart new file mode 100644 index 000000000000..dccb30754f14 --- /dev/null +++ b/packages/camera/camera_platform_interface/test/utils/utils_test.dart @@ -0,0 +1,33 @@ +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/utils/utils.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Utility methods', () { + test( + 'Should return CameraLensDirection when valid value is supplied when parsing camera lens direction', + () { + expect( + parseCameraLensDirection('back'), + CameraLensDirection.back, + ); + expect( + parseCameraLensDirection('front'), + CameraLensDirection.front, + ); + expect( + parseCameraLensDirection('external'), + CameraLensDirection.external, + ); + }); + + test( + 'Should throw ArgumentException when invalid value is supplied when parsing camera lens direction', + () { + expect( + () => parseCameraLensDirection('test'), + throwsA(isArgumentError), + ); + }); + }); +} From 4cbb191e5e79c72665fb3f1dd2863b4c1a555b71 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 3 Dec 2020 14:51:46 +0100 Subject: [PATCH 73/89] Flash WIP --- .../io/flutter/plugins/camera/Camera.java | 67 +++++++++++++++++++ .../plugins/camera/MethodCallHandlerImpl.java | 9 +++ .../android/app/src/main/AndroidManifest.xml | 1 + packages/camera/camera/example/lib/main.dart | 54 +++++++++++++++ .../camera/camera/ios/Classes/CameraPlugin.m | 43 ++++++++++++ .../camera/lib/src/camera_controller.dart | 20 +++++- .../method_channel/method_channel_camera.dart | 10 +++ .../platform_interface/camera_platform.dart | 5 ++ .../lib/src/types/flash_mode.dart | 15 +++++ .../lib/src/types/types.dart | 1 + .../test/camera_platform_interface_test.dart | 13 ++++ .../method_channel_camera_test.dart | 23 +++++++ 12 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart 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 2a4f1f5ff008..f18aecc18d1b 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 @@ -22,6 +22,7 @@ import android.media.ImageReader; import android.media.MediaRecorder; import android.os.Build; +import android.util.Log; import android.util.Size; import android.view.OrientationEventListener; import android.view.Surface; @@ -63,6 +64,7 @@ public class Camera { private CamcorderProfile recordingProfile; private int currentOrientation = ORIENTATION_UNKNOWN; private Context applicationContext; + private FlashMode flashMode; // Mirrors camera.dart public enum ResolutionPreset { @@ -74,6 +76,34 @@ public enum ResolutionPreset { max, } + // Mirrors flash_mode.dart + public enum FlashMode { + off(CaptureRequest.CONTROL_AE_MODE_ON, CaptureRequest.FLASH_MODE_OFF), + auto(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH, CaptureRequest.FLASH_MODE_OFF), + always(CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH, CaptureRequest.FLASH_MODE_SINGLE); + + int controlAEMode; + int flashMode; + + FlashMode(int controlAEMode, int flashMode) { + this.controlAEMode = controlAEMode; + this.flashMode = flashMode; + } + + public static FlashMode getValueForString(String modeStr) { + try { + return valueOf(modeStr); + } catch (IllegalArgumentException | NullPointerException e) { + return null; + } + } + + @Override + public String toString() { + return "FlashMode{" + "controlAEMode=" + controlAEMode + ", flashMode=" + flashMode + '}'; + } + } + public Camera( final Activity activity, final SurfaceTextureEntry flutterTexture, @@ -91,6 +121,7 @@ public Camera( this.dartMessenger = dartMessenger; this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); this.applicationContext = activity.getApplicationContext(); + this.flashMode = FlashMode.auto; orientationEventListener = new OrientationEventListener(activity.getApplicationContext()) { @Override @@ -240,6 +271,10 @@ public void takePicture(@NonNull final Result result) { cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(pictureImageReader.getSurface()); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); + captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); + captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, flashMode.controlAEMode); + captureBuilder.set(CaptureRequest.FLASH_MODE, flashMode.flashMode); + Log.d("FLASH_MODE", "SETTING FLASH MODE " + flashMode.toString()); cameraCaptureSession.capture( captureBuilder.build(), @@ -310,6 +345,8 @@ public void onConfigured(@NonNull CameraCaptureSession session) { cameraCaptureSession = session; captureRequestBuilder.set( CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, flashMode.controlAEMode); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, flashMode.flashMode); cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); if (onSuccessCallback != null) { onSuccessCallback.run(); @@ -416,6 +453,36 @@ public void resumeVideoRecording(@NonNull final Result result) { result.success(null); } + public void setFlashMode(@NonNull final Result result, String modeStr) { + // Get the flash availability + Boolean flashAvailable; + try { + flashAvailable = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.FLASH_INFO_AVAILABLE); + if (flashAvailable == null || !flashAvailable) flashAvailable = false; + } catch (CameraAccessException e) { + result.error("setFlashModeFailed", e.getMessage(), null); + return; + } + // Check if flash is available. + if (!flashAvailable) { + result.error("setFlashModeFailed", "Device does not have flash capabilities", null); + return; + } + // Get flash + FlashMode mode = FlashMode.getValueForString(modeStr); + if (mode == null) { + result.error("setFlashModeFailed", "Unknown flash mode " + modeStr, null); + return; + } + this.flashMode = mode; + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, mode.controlAEMode); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, mode.flashMode); + result.success(null); + } + public void startPreview() throws CameraAccessException { if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; 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 6c2e65e76f9e..b77949902a44 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 @@ -122,6 +122,15 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) camera.resumeVideoRecording(result); break; } + case "setFlashMode": + { + try { + camera.setFlashMode(result, call.argument("mode")); + } catch (Exception e) { + handleException(e, result); + } + break; + } case "startImageStream": { try { diff --git a/packages/camera/camera/example/android/app/src/main/AndroidManifest.xml b/packages/camera/camera/example/android/app/src/main/AndroidManifest.xml index f37f82306024..f216a7251bcf 100644 --- a/packages/camera/camera/example/android/app/src/main/AndroidManifest.xml +++ b/packages/camera/camera/example/android/app/src/main/AndroidManifest.xml @@ -37,4 +37,5 @@ android:required="true"/> + diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index e1edc1b06386..847779dade09 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -101,6 +101,7 @@ class _CameraExampleHomeState extends State ), ), _captureControlRowWidget(), + _flashModeRowWidget(), _toggleAudioWidget(), Padding( padding: const EdgeInsets.all(5.0), @@ -191,6 +192,43 @@ class _CameraExampleHomeState extends State ); } + /// Display a bar with buttons to change the flash mode + Widget _flashModeRowWidget() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + children: [ + IconButton( + icon: const Icon(Icons.flash_off), + color: controller?.value?.flashMode == FlashMode.off + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onFlashModeButtonPressed(FlashMode.off) + : null, + ), + IconButton( + icon: const Icon(Icons.flash_auto), + color: controller?.value?.flashMode == FlashMode.auto + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onFlashModeButtonPressed(FlashMode.auto) + : null, + ), + IconButton( + icon: const Icon(Icons.flash_on), + color: controller?.value?.flashMode == FlashMode.always + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onFlashModeButtonPressed(FlashMode.always) + : null, + ), + ], + ); + } + /// Display the control bar with buttons to take pictures and record videos. Widget _captureControlRowWidget() { return Row( @@ -317,6 +355,13 @@ class _CameraExampleHomeState extends State }); } + void onFlashModeButtonPressed(FlashMode mode) { + setFlashMode(mode).then((_) { + if (mounted) setState(() {}); + showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); + }); + } + void onVideoRecordButtonPressed() { startVideoRecording().then((_) { if (mounted) setState(() {}); @@ -406,6 +451,15 @@ class _CameraExampleHomeState extends State } } + Future setFlashMode(FlashMode mode) async { + try { + await controller.setFlashMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + Future _startVideoPlayer() async { final VideoPlayerController vController = VideoPlayerController.file(File(videoFile.path)); diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index dbff59e64f9a..ed48a9839dc1 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -114,6 +114,24 @@ - (UIImageOrientation)getImageRotation { } @end +static AVCaptureFlashMode getFlashModeForString(NSString *mode) { + if ([mode isEqualToString:@"off"]) { + return AVCaptureFlashModeOff; + } else if ([mode isEqualToString:@"auto"]) { + return AVCaptureFlashModeAuto; + } else if ([mode isEqualToString:@"always"]) { + return AVCaptureFlashModeOn; + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown flash mode %@", mode] + }]; + @throw error; + } +} + // Mirrors ResolutionPreset in camera.dart typedef enum { veryLow, @@ -181,6 +199,7 @@ @interface FLTCam : NSObject *)messenger { if (!_isStreamingImages) { FlutterEventChannel *eventChannel = @@ -907,6 +948,8 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [_camera pauseVideoRecordingWithResult:result]; } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { [_camera resumeVideoRecordingWithResult:result]; + } else if ([@"setFlashMode" isEqualToString:call.method]) { + [_camera setFlashModeWithResult:result mode:call.arguments[@"mode"]]; } else { result(FlutterMethodNotImplemented); } diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index d41fe512ae45..877d81b301f6 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -30,6 +30,7 @@ class CameraValue { this.isTakingPicture, this.isStreamingImages, bool isRecordingPaused, + this.flashMode, }) : _isRecordingPaused = isRecordingPaused; /// Creates a new camera controller state for an uninitialized controller. @@ -40,6 +41,7 @@ class CameraValue { isTakingPicture: false, isStreamingImages: false, isRecordingPaused: false, + flashMode: FlashMode.auto, ); /// True after [CameraController.initialize] has completed successfully. @@ -80,6 +82,9 @@ class CameraValue { /// When true [errorDescription] describes the error. bool get hasError => errorDescription != null; + /// The flash mode the camera is currently set to. + final FlashMode flashMode; + /// Creates a modified copy of the object. /// /// Explicitly specified fields get the specified value, all other fields get @@ -92,6 +97,7 @@ class CameraValue { String errorDescription, Size previewSize, bool isRecordingPaused, + FlashMode cameraFlashMode, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, @@ -101,6 +107,7 @@ class CameraValue { isTakingPicture: isTakingPicture ?? this.isTakingPicture, isStreamingImages: isStreamingImages ?? this.isStreamingImages, isRecordingPaused: isRecordingPaused ?? _isRecordingPaused, + flashMode: cameraFlashMode ?? this.flashMode, ); } @@ -111,7 +118,8 @@ class CameraValue { 'isInitialized: $isInitialized, ' 'errorDescription: $errorDescription, ' 'previewSize: $previewSize, ' - 'isStreamingImages: $isStreamingImages)'; + 'isStreamingImages: $isStreamingImages' + 'cameraFlashMode: $flashMode)'; } } @@ -454,6 +462,16 @@ class CameraController extends ValueNotifier { } } + /// Sets the flash mode for taking pictures. + Future setFlashMode(FlashMode mode) async { + try { + await CameraPlatform.instance.setFlashMode(_cameraId, mode); + value = value.copyWith(cameraFlashMode: mode); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + /// Releases the resources of this camera. @override Future dispose() async { 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 13785fca36ce..1344163920d8 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 @@ -174,6 +174,16 @@ class MethodChannelCamera extends CameraPlatform { {'cameraId': cameraId}, ); + @override + Future setFlashMode(int cameraId, FlashMode mode) => + _channel.invokeMethod( + 'setFlashMode', + { + 'cameraId': cameraId, + 'mode': mode.toString().split('.').last, + }, + ); + @override Widget buildPreview(int cameraId) { return Texture(textureId: 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 c192ebd7f454..d6ac470d0814 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 @@ -109,6 +109,11 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('resumeVideoRecording() is not implemented.'); } + /// Sets the flash mode for taking pictures. + Future setFlashMode(int cameraId, FlashMode mode) { + throw UnimplementedError('setFlashMode() is not implemented.'); + } + /// Returns a widget showing a live camera preview. Widget buildPreview(int cameraId) { throw UnimplementedError('buildView() has not been implemented.'); diff --git a/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart new file mode 100644 index 000000000000..6ed92e4801eb --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart @@ -0,0 +1,15 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// The possible flash modes that can be set for a camera +enum FlashMode { + /// Do not use the flash when taking a picture. + off, + + /// Let the device decide whether to flash the camera when taking a picture. + auto, + + /// Always use the flash when taking a picture. + always, +} diff --git a/packages/camera/camera_platform_interface/lib/src/types/types.dart b/packages/camera/camera_platform_interface/lib/src/types/types.dart index 71e7a97ef49a..3a89a1021e95 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/types.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/types.dart @@ -5,3 +5,4 @@ export 'camera_description.dart'; export 'resolution_preset.dart'; export 'camera_exception.dart'; +export 'flash_mode.dart'; diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index 2bc35bf4055c..52b1b3ea34c4 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -173,6 +173,19 @@ void main() { ); }); + test( + 'Default implementation of setFlashMode() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.setFlashMode(1, null), + throwsUnimplementedError, + ); + }); + test( 'Default implementation of startVideoRecording() should throw unimplemented error', () { 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 1dfa22b87f97..372d14e864ee 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 @@ -469,6 +469,29 @@ void main() { ]); }); + test('Should set the flash mode', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFlashMode': null}, + ); + + // Act + await camera.setFlashMode(cameraId, FlashMode.always); + await camera.setFlashMode(cameraId, FlashMode.auto); + await camera.setFlashMode(cameraId, FlashMode.off); + + // Assert + expect(channel.log, [ + isMethodCall('setFlashMode', + arguments: {'cameraId': cameraId, 'mode': 'ALWAYS'}), + isMethodCall('setFlashMode', + arguments: {'cameraId': cameraId, 'mode': 'AUTO'}), + isMethodCall('setFlashMode', + arguments: {'cameraId': cameraId, 'mode': 'OFF'}), + ]); + }); + test('Should build a texture widget as preview widget', () async { // Act Widget widget = camera.buildPreview(cameraId); From 884a24e7a04dcf35fff2039fec20488e321c3a02 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 4 Dec 2020 00:20:21 +0100 Subject: [PATCH 74/89] Implement flash modes for Android --- .../io/flutter/plugins/camera/Camera.java | 184 ++++++++++++------ 1 file changed, 128 insertions(+), 56 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 f18aecc18d1b..74db0f62d769 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 @@ -16,13 +16,14 @@ import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.CamcorderProfile; import android.media.Image; import android.media.ImageReader; import android.media.MediaRecorder; import android.os.Build; -import android.util.Log; import android.util.Size; import android.view.OrientationEventListener; import android.view.Surface; @@ -65,6 +66,7 @@ public class Camera { private int currentOrientation = ORIENTATION_UNKNOWN; private Context applicationContext; private FlashMode flashMode; + private PictureCaptureRequest pictureCaptureRequest; // Mirrors camera.dart public enum ResolutionPreset { @@ -78,17 +80,9 @@ public enum ResolutionPreset { // Mirrors flash_mode.dart public enum FlashMode { - off(CaptureRequest.CONTROL_AE_MODE_ON, CaptureRequest.FLASH_MODE_OFF), - auto(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH, CaptureRequest.FLASH_MODE_OFF), - always(CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH, CaptureRequest.FLASH_MODE_SINGLE); - - int controlAEMode; - int flashMode; - - FlashMode(int controlAEMode, int flashMode) { - this.controlAEMode = controlAEMode; - this.flashMode = flashMode; - } + off, + auto, + always; public static FlashMode getValueForString(String modeStr) { try { @@ -97,11 +91,6 @@ public static FlashMode getValueForString(String modeStr) { return null; } } - - @Override - public String toString() { - return "FlashMode{" + "controlAEMode=" + controlAEMode + ", flashMode=" + flashMode + '}'; - } } public Camera( @@ -245,62 +234,125 @@ SurfaceTextureEntry getFlutterTexture() { } public void takePicture(@NonNull final Result result) { + // Only take 1 picture at a time + if (pictureCaptureRequest != null && !pictureCaptureRequest.isFinished()) { + result.error("captureAlreadyActive", "Picture is currently already being captured", null); + return; + } + // Store the result + this.pictureCaptureRequest = new PictureCaptureRequest(result); + + // Create temporary file final File outputDir = applicationContext.getCacheDir(); final File file; try { file = File.createTempFile("CAP", ".jpg", outputDir); } catch (IOException e) { - result.error("cannotCreateFile", e.getMessage(), null); + pictureCaptureRequest.error("cannotCreateFile", e.getMessage(), null); return; } + // Listen for picture being taken pictureImageReader.setOnImageAvailableListener( reader -> { try (Image image = reader.acquireLatestImage()) { ByteBuffer buffer = image.getPlanes()[0].getBuffer(); writeToFile(buffer, file); - result.success(file.getAbsolutePath()); + pictureCaptureRequest.finish(file.getAbsolutePath()); } catch (IOException e) { - result.error("IOError", "Failed saving image", null); + pictureCaptureRequest.error("IOError", "Failed saving image", null); } }, null); + runPicturePreCapture(); + } + + private final CameraCaptureSession.CaptureCallback pictureCaptureCallback = + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureCompleted( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult result) { + assert (pictureCaptureRequest != null); + switch (pictureCaptureRequest.getState()) { + case awaitingPreCapture: + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); + // Some devices might return null here, in which case we will also continue. + if (aeState == null + || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED + || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) { + runPictureCapture(); + } + break; + } + } + + @Override + public void onCaptureFailed( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureFailure failure) { + assert (pictureCaptureRequest != null); + String reason; + switch (failure.getReason()) { + case CaptureFailure.REASON_ERROR: + reason = "An error happened in the framework"; + break; + case CaptureFailure.REASON_FLUSHED: + reason = "The capture has failed due to an abortCaptures() call"; + break; + default: + reason = "Unknown reason"; + } + pictureCaptureRequest.error("captureFailure", reason, null); + } + }; + + private void runPicturePreCapture() { + assert (pictureCaptureRequest != null); + pictureCaptureRequest.setState(PictureCaptureRequest.State.awaitingPreCapture); + + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); + try { + cameraCaptureSession.capture(captureRequestBuilder.build(), pictureCaptureCallback, null); + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE); + } catch (CameraAccessException e) { + pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); + } + } + + private void runPictureCapture() { + assert (pictureCaptureRequest != null); + pictureCaptureRequest.setState(PictureCaptureRequest.State.capturing); try { final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(pictureImageReader.getSurface()); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); - captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); - captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, flashMode.controlAEMode); - captureBuilder.set(CaptureRequest.FLASH_MODE, flashMode.flashMode); - Log.d("FLASH_MODE", "SETTING FLASH MODE " + flashMode.toString()); - - cameraCaptureSession.capture( - captureBuilder.build(), - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureFailed( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull CaptureFailure failure) { - String reason; - switch (failure.getReason()) { - case CaptureFailure.REASON_ERROR: - reason = "An error happened in the framework"; - break; - case CaptureFailure.REASON_FLUSHED: - reason = "The capture has failed due to an abortCaptures() call"; - break; - default: - reason = "Unknown reason"; - } - result.error("captureFailure", reason, null); - } - }, - null); + switch (flashMode) { + case off: + captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); + captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); + break; + case auto: + captureBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + break; + case always: + default: + captureBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); + break; + } + cameraCaptureSession.capture(captureBuilder.build(), pictureCaptureCallback, null); } catch (CameraAccessException e) { - result.error("cameraAccess", e.getMessage(), null); + pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); } } @@ -343,10 +395,7 @@ public void onConfigured(@NonNull CameraCaptureSession session) { return; } cameraCaptureSession = session; - captureRequestBuilder.set( - CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); - captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, flashMode.controlAEMode); - captureRequestBuilder.set(CaptureRequest.FLASH_MODE, flashMode.flashMode); + initPreviewCaptureBuilder(); cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); if (onSuccessCallback != null) { onSuccessCallback.run(); @@ -453,7 +502,8 @@ public void resumeVideoRecording(@NonNull final Result result) { result.success(null); } - public void setFlashMode(@NonNull final Result result, String modeStr) { + public void setFlashMode(@NonNull final Result result, String modeStr) + throws CameraAccessException { // Get the flash availability Boolean flashAvailable; try { @@ -478,11 +528,33 @@ public void setFlashMode(@NonNull final Result result, String modeStr) { return; } this.flashMode = mode; - captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, mode.controlAEMode); - captureRequestBuilder.set(CaptureRequest.FLASH_MODE, mode.flashMode); + initPreviewCaptureBuilder(); + this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); result.success(null); } + private void initPreviewCaptureBuilder() { + captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); + switch (flashMode) { + case off: + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); + break; + case auto: + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); + break; + case always: + default: + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); + break; + } + } + public void startPreview() throws CameraAccessException { if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; From b38bfa98b30d11bf5d19cfe337d79e1fdf34218e Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 4 Dec 2020 09:20:33 +0100 Subject: [PATCH 75/89] Update dependency to git repo --- packages/camera/camera/pubspec.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index b3e6efeea2a1..1fd278ba1604 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -13,7 +13,10 @@ dependencies: sdk: flutter # TODO(mvanbeusekom): Update dependency to pub.dev version once camera_platform_interface is published camera_platform_interface: - path: ../camera_platform_interface + git: + url: https://github.com/flutter/plugins.git + path: packages/camera/camera_platform_interface + ref: master dev_dependencies: path_provider: ^0.5.0 From d7b2e1cd1c01bcc546791323e81e9c4b63b492e9 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 4 Dec 2020 11:43:19 +0100 Subject: [PATCH 76/89] Add missing PictureCaptureRequest class --- .../plugins/camera/PictureCaptureRequest.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java new file mode 100644 index 000000000000..e365f071d9a8 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java @@ -0,0 +1,49 @@ +package io.flutter.plugins.camera; + +import androidx.annotation.Nullable; +import io.flutter.plugin.common.MethodChannel; + +class PictureCaptureRequest { + + enum State { + idle, + awaitingPreCapture, + capturing, + finished, + error, + } + + private final MethodChannel.Result result; + private State state; + + public PictureCaptureRequest(MethodChannel.Result result) { + this.result = result; + state = State.idle; + } + + public void setState(State state) { + if (isFinished()) throw new IllegalStateException("Request has already been finished"); + this.state = state; + } + + public State getState() { + return state; + } + + public boolean isFinished() { + return state == State.finished || state == State.error; + } + + public void finish(String absolutePath) { + if (isFinished()) throw new IllegalStateException("Request has already been finished"); + result.success(absolutePath); + state = State.finished; + } + + public void error( + String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { + if (isFinished()) throw new IllegalStateException("Request has already been finished"); + result.error(errorCode, errorMessage, errorDetails); + state = State.error; + } +} From 7448e04342dc3c72216dcbead4af7467d389eddb Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 4 Dec 2020 12:08:14 +0100 Subject: [PATCH 77/89] Add PR feedback --- .../main/java/io/flutter/plugins/camera/Camera.java | 11 +++-------- .../flutter/plugins/camera/MethodCallHandlerImpl.java | 8 +++++++- 2 files changed, 10 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 74db0f62d769..c5df64e39457 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 @@ -502,7 +502,7 @@ public void resumeVideoRecording(@NonNull final Result result) { result.success(null); } - public void setFlashMode(@NonNull final Result result, String modeStr) + public void setFlashMode(@NonNull final Result result, FlashMode mode) throws CameraAccessException { // Get the flash availability Boolean flashAvailable; @@ -511,22 +511,17 @@ public void setFlashMode(@NonNull final Result result, String modeStr) cameraManager .getCameraCharacteristics(cameraDevice.getId()) .get(CameraCharacteristics.FLASH_INFO_AVAILABLE); - if (flashAvailable == null || !flashAvailable) flashAvailable = false; } catch (CameraAccessException e) { result.error("setFlashModeFailed", e.getMessage(), null); return; } // Check if flash is available. - if (!flashAvailable) { + if (flashAvailable == null || !flashAvailable) { result.error("setFlashModeFailed", "Device does not have flash capabilities", null); return; } // Get flash - FlashMode mode = FlashMode.getValueForString(modeStr); - if (mode == null) { - result.error("setFlashModeFailed", "Unknown flash mode " + modeStr, null); - return; - } + this.flashMode = mode; initPreviewCaptureBuilder(); this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); 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 b77949902a44..4ed1782da073 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 @@ -124,8 +124,14 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) } case "setFlashMode": { + String modeStr = call.argument("mode"); + Camera.FlashMode mode = Camera.FlashMode.getValueForString(modeStr); + if (mode == null) { + result.error("setFlashModeFailed", "Unknown flash mode " + modeStr, null); + return; + } try { - camera.setFlashMode(result, call.argument("mode")); + camera.setFlashMode(result, mode); } catch (Exception e) { handleException(e, result); } From c469adb1577b66e598298f8e79a732de421d5d47 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 4 Dec 2020 12:19:30 +0100 Subject: [PATCH 78/89] Move enums out of Camera.java --- .../io/flutter/plugins/camera/Camera.java | 27 ++----------------- .../flutter/plugins/camera/CameraUtils.java | 3 ++- .../plugins/camera/MethodCallHandlerImpl.java | 3 ++- .../plugins/camera/types/FlashMode.java | 16 +++++++++++ .../camera/types/ResolutionPreset.java | 11 ++++++++ 5 files changed, 33 insertions(+), 27 deletions(-) create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java 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 c5df64e39457..64b72ca19ddb 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 @@ -31,6 +31,8 @@ import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.media.MediaRecorderBuilder; +import io.flutter.plugins.camera.types.FlashMode; +import io.flutter.plugins.camera.types.ResolutionPreset; import io.flutter.view.TextureRegistry.SurfaceTextureEntry; import java.io.File; import java.io.FileOutputStream; @@ -68,31 +70,6 @@ public class Camera { private FlashMode flashMode; private PictureCaptureRequest pictureCaptureRequest; - // Mirrors camera.dart - public enum ResolutionPreset { - low, - medium, - high, - veryHigh, - ultraHigh, - max, - } - - // Mirrors flash_mode.dart - public enum FlashMode { - off, - auto, - always; - - public static FlashMode getValueForString(String modeStr) { - try { - return valueOf(modeStr); - } catch (IllegalArgumentException | NullPointerException e) { - return null; - } - } - } - public Camera( final Activity activity, final SurfaceTextureEntry flutterTexture, diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java index a7bb3b7d4914..af610ac93645 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java @@ -10,7 +10,8 @@ import android.hardware.camera2.params.StreamConfigurationMap; import android.media.CamcorderProfile; import android.util.Size; -import io.flutter.plugins.camera.Camera.ResolutionPreset; +import io.flutter.plugins.camera.types.ResolutionPreset; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; 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 4ed1782da073..12b99b72f642 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 @@ -10,6 +10,7 @@ import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry; +import io.flutter.plugins.camera.types.FlashMode; import io.flutter.view.TextureRegistry; import java.util.HashMap; import java.util.Map; @@ -125,7 +126,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) case "setFlashMode": { String modeStr = call.argument("mode"); - Camera.FlashMode mode = Camera.FlashMode.getValueForString(modeStr); + FlashMode mode = FlashMode.getValueForString(modeStr); if (mode == null) { result.error("setFlashModeFailed", "Unknown flash mode " + modeStr, null); return; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java new file mode 100644 index 000000000000..b4900cdd2aa6 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java @@ -0,0 +1,16 @@ +package io.flutter.plugins.camera.types; + +// Mirrors flash_mode.dart +public enum FlashMode { + off, + auto, + always; + + public static FlashMode getValueForString(String modeStr) { + try { + return valueOf(modeStr); + } catch (IllegalArgumentException | NullPointerException e) { + return null; + } + } +} \ No newline at end of file diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java new file mode 100644 index 000000000000..07497e5b5e74 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java @@ -0,0 +1,11 @@ +package io.flutter.plugins.camera.types; + +// Mirrors camera.dart +public enum ResolutionPreset { + low, + medium, + high, + veryHigh, + ultraHigh, + max, +} \ No newline at end of file From 6fc15935277e15a2ff91719090d7398dea24cdff Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 9 Dec 2020 09:41:47 +0100 Subject: [PATCH 79/89] Expanded platform interface so support setting flash mode --- .../camera_platform_interface/CHANGELOG.md | 4 ++++ .../method_channel/method_channel_camera.dart | 10 ++++++++ .../platform_interface/camera_platform.dart | 5 ++++ .../lib/src/types/flash_mode.dart | 15 ++++++++++++ .../lib/src/types/types.dart | 1 + .../camera_platform_interface/pubspec.yaml | 2 +- .../test/camera_platform_interface_test.dart | 13 +++++++++++ .../method_channel_camera_test.dart | 23 +++++++++++++++++++ .../test/types/flash_mode_test.dart | 22 ++++++++++++++++++ 9 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart create mode 100644 packages/camera/camera_platform_interface/test/types/flash_mode_test.dart diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index 75d169ea1d6a..22efaaa3f85b 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.1 + +- Added interface methods for setting flash mode. + ## 1.0.0 - Initial open-source release 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 c649a41f5e9f..cbc2016b65d4 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 @@ -175,6 +175,16 @@ class MethodChannelCamera extends CameraPlatform { {'cameraId': cameraId}, ); + @override + Future setFlashMode(int cameraId, FlashMode mode) => + _channel.invokeMethod( + 'setFlashMode', + { + 'cameraId': cameraId, + 'mode': mode.toString().split('.').last, + }, + ); + @override Widget buildPreview(int cameraId) { return Texture(textureId: 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 5281a423459a..5a1568b45a43 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 @@ -108,6 +108,11 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('resumeVideoRecording() is not implemented.'); } + /// Sets the flash mode for taking pictures. + Future setFlashMode(int cameraId, FlashMode mode) { + throw UnimplementedError('setFlashMode() is not implemented.'); + } + /// Returns a widget showing a live camera preview. Widget buildPreview(int cameraId) { throw UnimplementedError('buildView() has not been implemented.'); diff --git a/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart new file mode 100644 index 000000000000..ffbbee2f18c6 --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart @@ -0,0 +1,15 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// The possible flash modes that can be set for a camera +enum FlashMode { + /// Do not use the flash when taking a picture. + off, + + /// Let the device decide whether to flash the camera when taking a picture. + auto, + + /// Always use the flash when taking a picture. + always, +} \ No newline at end of file diff --git a/packages/camera/camera_platform_interface/lib/src/types/types.dart b/packages/camera/camera_platform_interface/lib/src/types/types.dart index 71e7a97ef49a..3a89a1021e95 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/types.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/types.dart @@ -5,3 +5,4 @@ export 'camera_description.dart'; export 'resolution_preset.dart'; export 'camera_exception.dart'; +export 'flash_mode.dart'; diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index c2bb6fcc5963..e29f7979f8b3 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.0 +version: 1.0.1 dependencies: flutter: diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index 2bc35bf4055c..e7f9037e11c7 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -173,6 +173,19 @@ void main() { ); }); + test( + 'Default implementation of setFlashMode() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.setFlashMode(1, null), + throwsUnimplementedError, + ); + }); + test( 'Default implementation of startVideoRecording() should throw unimplemented error', () { 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 952a78e3408b..a42d9ab9769c 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 @@ -470,6 +470,29 @@ void main() { ]); }); + test('Should set the flash mode', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFlashMode': null}, + ); + + // Act + await camera.setFlashMode(cameraId, FlashMode.always); + await camera.setFlashMode(cameraId, FlashMode.auto); + await camera.setFlashMode(cameraId, FlashMode.off); + + // Assert + expect(channel.log, [ + isMethodCall('setFlashMode', + arguments: {'cameraId': cameraId, 'mode': 'always'}), + isMethodCall('setFlashMode', + arguments: {'cameraId': cameraId, 'mode': 'auto'}), + isMethodCall('setFlashMode', + arguments: {'cameraId': cameraId, 'mode': 'off'}), + ]); + }); + test('Should build a texture widget as preview widget', () async { // Act Widget widget = camera.buildPreview(cameraId); diff --git a/packages/camera/camera_platform_interface/test/types/flash_mode_test.dart b/packages/camera/camera_platform_interface/test/types/flash_mode_test.dart new file mode 100644 index 000000000000..59726acf7873 --- /dev/null +++ b/packages/camera/camera_platform_interface/test/types/flash_mode_test.dart @@ -0,0 +1,22 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// 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'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('FlashMode should contain 3 options', () { + final values = FlashMode.values; + + expect(values.length, 3); + }); + + test("FlashMode enum should have items in correct index", () { + final values = FlashMode.values; + + expect(values[0], FlashMode.off); + expect(values[1], FlashMode.auto); + expect(values[2], FlashMode.always); + }); +} From ab5a338801d3474b1ac807e13a3510d2d09d75d1 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 9 Dec 2020 09:44:37 +0100 Subject: [PATCH 80/89] Formatted dart code --- .../lib/src/types/flash_mode.dart | 2 +- .../test/camera_platform_interface_test.dart | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart index ffbbee2f18c6..6ed92e4801eb 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart @@ -12,4 +12,4 @@ enum FlashMode { /// Always use the flash when taking a picture. always, -} \ No newline at end of file +} diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index e7f9037e11c7..52b1b3ea34c4 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -175,16 +175,16 @@ void main() { test( 'Default implementation of setFlashMode() should throw unimplemented error', - () { - // Arrange - final cameraPlatform = ExtendsCameraPlatform(); - - // Act & Assert - expect( - () => cameraPlatform.setFlashMode(1, null), - throwsUnimplementedError, - ); - }); + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.setFlashMode(1, null), + throwsUnimplementedError, + ); + }); test( 'Default implementation of startVideoRecording() should throw unimplemented error', From 6902ea85c29793a0fa6276ef4fdeedb38c2f00ba Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 10 Dec 2020 09:43:38 +0100 Subject: [PATCH 81/89] Manually serialize flash mode enum rather than relying on stringification. --- .../src/method_channel/method_channel_camera.dart | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 cbc2016b65d4..246077f5e0c3 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 @@ -181,7 +181,7 @@ class MethodChannelCamera extends CameraPlatform { 'setFlashMode', { 'cameraId': cameraId, - 'mode': mode.toString().split('.').last, + 'mode': _serializeFlashMode(mode), }, ); @@ -190,6 +190,18 @@ class MethodChannelCamera extends CameraPlatform { return Texture(textureId: cameraId); } + /// Returns the flash mode as a String. + String _serializeFlashMode(FlashMode flashMode) { + switch (flashMode) { + case FlashMode.off: + return 'off'; + case FlashMode.auto: + return 'auto'; + case FlashMode.always: + return 'always'; + } + } + /// Returns the resolution preset as a String. String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { switch (resolutionPreset) { From 9c0d01572059d1e86651f3a1e3e58ac59ecf2543 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 10 Dec 2020 10:04:43 +0100 Subject: [PATCH 82/89] Add default to flash mode serialization --- .../lib/src/method_channel/method_channel_camera.dart | 2 ++ 1 file changed, 2 insertions(+) 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 246077f5e0c3..3086ae018d57 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 @@ -199,6 +199,8 @@ class MethodChannelCamera extends CameraPlatform { return 'auto'; case FlashMode.always: return 'always'; + default: + throw ArgumentError('Unknown FlashMode value'); } } From fc0f410d68f5ab420090734d57fe65535d3e5b7b Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 10 Dec 2020 15:04:38 +0100 Subject: [PATCH 83/89] Fix for unit tests and reformatting --- .../flutter/plugins/camera/CameraUtils.java | 1 - .../plugins/camera/types/FlashMode.java | 20 +++++++++---------- .../camera/types/ResolutionPreset.java | 14 ++++++------- .../camera/lib/src/camera_controller.dart | 10 +++++----- packages/camera/camera/pubspec.yaml | 10 ++-------- .../camera/camera/test/camera_value_test.dart | 5 ++++- 6 files changed, 28 insertions(+), 32 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java index af610ac93645..3b665d6b24f2 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java @@ -11,7 +11,6 @@ import android.media.CamcorderProfile; import android.util.Size; import io.flutter.plugins.camera.types.ResolutionPreset; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java index b4900cdd2aa6..eddeddc47eab 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java @@ -2,15 +2,15 @@ // Mirrors flash_mode.dart public enum FlashMode { - off, - auto, - always; + off, + auto, + always; - public static FlashMode getValueForString(String modeStr) { - try { - return valueOf(modeStr); - } catch (IllegalArgumentException | NullPointerException e) { - return null; - } + public static FlashMode getValueForString(String modeStr) { + try { + return valueOf(modeStr); + } catch (IllegalArgumentException | NullPointerException e) { + return null; } -} \ No newline at end of file + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java index 07497e5b5e74..ffbe2e62095d 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java @@ -2,10 +2,10 @@ // Mirrors camera.dart public enum ResolutionPreset { - low, - medium, - high, - veryHigh, - ultraHigh, - max, -} \ No newline at end of file + low, + medium, + high, + veryHigh, + ultraHigh, + max, +} diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index d35b79c749e0..8124ee14fbde 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -104,7 +104,7 @@ class CameraValue { String errorDescription, Size previewSize, bool isRecordingPaused, - FlashMode cameraFlashMode, + FlashMode flashMode, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, @@ -114,7 +114,7 @@ class CameraValue { isTakingPicture: isTakingPicture ?? this.isTakingPicture, isStreamingImages: isStreamingImages ?? this.isStreamingImages, isRecordingPaused: isRecordingPaused ?? _isRecordingPaused, - flashMode: cameraFlashMode ?? this.flashMode, + flashMode: flashMode ?? this.flashMode, ); } @@ -125,8 +125,8 @@ class CameraValue { 'isInitialized: $isInitialized, ' 'errorDescription: $errorDescription, ' 'previewSize: $previewSize, ' - 'isStreamingImages: $isStreamingImages' - 'cameraFlashMode: $flashMode)'; + 'isStreamingImages: $isStreamingImages, ' + 'flashMode: $flashMode)'; } } @@ -476,7 +476,7 @@ class CameraController extends ValueNotifier { Future setFlashMode(FlashMode mode) async { try { await CameraPlatform.instance.setFlashMode(_cameraId, mode); - value = value.copyWith(cameraFlashMode: mode); + value = value.copyWith(flashMode: mode); } 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 1fd278ba1604..21f30ef338b4 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -5,18 +5,12 @@ description: A Flutter plugin for getting information about and controlling the version: 0.6.0 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera -# TODO(mvanbeusekom): Remove when camera_platform_interface package is published. -publish_to: none - dependencies: flutter: sdk: flutter - # TODO(mvanbeusekom): Update dependency to pub.dev version once camera_platform_interface is published + # TODO(BeMacized): Update dependency to pub.dev version once updated camera_platform_interface is published camera_platform_interface: - git: - url: https://github.com/flutter/plugins.git - path: packages/camera/camera_platform_interface - ref: master + path: ../camera_platform_interface dev_dependencies: path_provider: ^0.5.0 diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart index 28255eb0a568..fdf07197b417 100644 --- a/packages/camera/camera/test/camera_value_test.dart +++ b/packages/camera/camera/test/camera_value_test.dart @@ -19,6 +19,7 @@ void main() { isRecordingVideo: false, isTakingPicture: false, isStreamingImages: false, + flashMode: FlashMode.auto, ); expect(cameraValue, isA()); @@ -56,6 +57,7 @@ void main() { expect(cameraValue.isRecordingVideo, isFalse); expect(cameraValue.isTakingPicture, isFalse); expect(cameraValue.isStreamingImages, isFalse); + expect(cameraValue.flashMode, FlashMode.auto); }); test('Has aspectRatio after setting size', () { @@ -93,10 +95,11 @@ void main() { isRecordingVideo: false, isTakingPicture: false, isStreamingImages: false, + flashMode: FlashMode.auto, ); expect(cameraValue.toString(), - 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false)'); + 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto)'); }); }); } From 048c7be3521767bfb7ebf674bb8c89c35d636cdc Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 14 Dec 2020 10:36:52 +0100 Subject: [PATCH 84/89] Fixed CHANGELOG and remove redundant iOS files --- packages/camera/camera/CHANGELOG.md | 8 +------- .../camera/example/ios/Flutter/.last_build_id | 1 - .../camera/example/ios/Flutter/Flutter.podspec | 18 ------------------ .../xcshareddata/IDEWorkspaceChecks.plist | 8 -------- .../example/ios/Flutter/Flutter.podspec | 18 ------------------ .../xcshareddata/IDEWorkspaceChecks.plist | 8 -------- 6 files changed, 1 insertion(+), 60 deletions(-) delete mode 100644 packages/camera/camera/example/ios/Flutter/.last_build_id delete mode 100644 packages/camera/camera/example/ios/Flutter/Flutter.podspec delete mode 100644 packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec delete mode 100644 packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 4bf242b988e9..8bf128d5086a 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -22,13 +22,7 @@ Method changes in `CameraController`: ## 0.5.8+17 -As part of implementing federated architecture and making the interface compatible with the web this version contains the following **breaking changes**: - -Method changes in `CameraController`: -- The `takePicture` method no longer accepts the `path` parameter, but instead returns the captured image as an instance of the `XFile` class; -- The `startVideoRecording` method no longer accepts the `filePath`. Instead the recorded video is now returned as a `XFile` instance when the `stopVideoRecording` method completes; -- The `stopVideoRecording` method now returns the captured video when it completes; -- Added the `buildPreview` method which is now used to implement the CameraPreview widget. +* Added Android 30 support. ## 0.5.8+16 diff --git a/packages/camera/camera/example/ios/Flutter/.last_build_id b/packages/camera/camera/example/ios/Flutter/.last_build_id deleted file mode 100644 index 1d0b0dc32be3..000000000000 --- a/packages/camera/camera/example/ios/Flutter/.last_build_id +++ /dev/null @@ -1 +0,0 @@ -5490cb309144ac61a67edda5c46bb18b \ No newline at end of file diff --git a/packages/camera/camera/example/ios/Flutter/Flutter.podspec b/packages/camera/camera/example/ios/Flutter/Flutter.podspec deleted file mode 100644 index 5ca30416bac0..000000000000 --- a/packages/camera/camera/example/ios/Flutter/Flutter.podspec +++ /dev/null @@ -1,18 +0,0 @@ -# -# NOTE: This podspec is NOT to be published. It is only used as a local source! -# - -Pod::Spec.new do |s| - s.name = 'Flutter' - s.version = '1.0.0' - s.summary = 'High-performance, high-fidelity mobile apps.' - s.description = <<-DESC -Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. - DESC - s.homepage = 'https://flutter.io' - s.license = { :type => 'MIT' } - s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } - s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } - s.ios.deployment_target = '8.0' - s.vendored_frameworks = 'Flutter.framework' -end diff --git a/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d68..000000000000 --- a/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec b/packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec deleted file mode 100644 index 5ca30416bac0..000000000000 --- a/packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Flutter.podspec +++ /dev/null @@ -1,18 +0,0 @@ -# -# NOTE: This podspec is NOT to be published. It is only used as a local source! -# - -Pod::Spec.new do |s| - s.name = 'Flutter' - s.version = '1.0.0' - s.summary = 'High-performance, high-fidelity mobile apps.' - s.description = <<-DESC -Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. - DESC - s.homepage = 'https://flutter.io' - s.license = { :type => 'MIT' } - s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } - s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } - s.ios.deployment_target = '8.0' - s.vendored_frameworks = 'Flutter.framework' -end diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d68..000000000000 --- a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - From 1694bcc05a395ee0705a64310bc214916e820cb7 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 14 Dec 2020 16:06:11 +0100 Subject: [PATCH 85/89] Expose FlashMode through camera package --- .../plugins/camera/DartMessengerTest.java | 1 - .../camera/camera/ios/Classes/CameraPlugin.m | 73 ++++++++++--------- packages/camera/camera/lib/camera.dart | 1 + 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index 3b08a86d46c6..a689f2b6128f 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -7,7 +7,6 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; -import io.flutter.plugins.camera.DartMessenger.EventType; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 6d7c17a97686..cc70ddf209ce 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -115,21 +115,21 @@ - (UIImageOrientation)getImageRotation { @end static AVCaptureFlashMode getFlashModeForString(NSString *mode) { - if ([mode isEqualToString:@"off"]) { - return AVCaptureFlashModeOff; - } else if ([mode isEqualToString:@"auto"]) { - return AVCaptureFlashModeAuto; - } else if ([mode isEqualToString:@"always"]) { - return AVCaptureFlashModeOn; - } else { - NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain - code:NSURLErrorUnknown - userInfo:@{ - NSLocalizedDescriptionKey : [NSString - stringWithFormat:@"Unknown flash mode %@", mode] - }]; - @throw error; - } + if ([mode isEqualToString:@"off"]) { + return AVCaptureFlashModeOff; + } else if ([mode isEqualToString:@"auto"]) { + return AVCaptureFlashModeAuto; + } else if ([mode isEqualToString:@"always"]) { + return AVCaptureFlashModeOn; + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown flash mode %@", mode] + }]; + @throw error; + } } // Mirrors ResolutionPreset in camera.dart @@ -673,24 +673,29 @@ - (void)resumeVideoRecordingWithResult:(FlutterResult)result { result(nil); } -- (void)setFlashModeWithResult:(FlutterResult)result mode:(NSString *) modeStr { - AVCaptureFlashMode mode; - @try { - mode = getFlashModeForString(modeStr); - } @catch (NSError *e) { - result(getFlutterError(e)); - return; - } - if (!_captureDevice.hasFlash) { - result([FlutterError errorWithCode:@"setFlashModeFailed" message:@"Device does not have flash capabilities" details:nil]); - return; - } - if (![_capturePhotoOutput.supportedFlashModes containsObject:[NSNumber numberWithInt:((int)mode)]]) { - result([FlutterError errorWithCode:@"setFlashModeFailed" message:@"Device does not support this specific flash mode" details:nil]); - return; - } - _flashMode = mode; - result(nil); +- (void)setFlashModeWithResult:(FlutterResult)result mode:(NSString *)modeStr { + AVCaptureFlashMode mode; + @try { + mode = getFlashModeForString(modeStr); + } @catch (NSError *e) { + result(getFlutterError(e)); + return; + } + if (!_captureDevice.hasFlash) { + result([FlutterError errorWithCode:@"setFlashModeFailed" + message:@"Device does not have flash capabilities" + details:nil]); + return; + } + if (![_capturePhotoOutput.supportedFlashModes + containsObject:[NSNumber numberWithInt:((int)mode)]]) { + result([FlutterError errorWithCode:@"setFlashModeFailed" + message:@"Device does not support this specific flash mode" + details:nil]); + return; + } + _flashMode = mode; + result(nil); } - (void)startImageStreamWithMessenger:(NSObject *)messenger { @@ -952,7 +957,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { [_camera resumeVideoRecordingWithResult:result]; } else if ([@"setFlashMode" isEqualToString:call.method]) { - [_camera setFlashModeWithResult:result mode:call.arguments[@"mode"]]; + [_camera setFlashModeWithResult:result mode:call.arguments[@"mode"]]; } else { result(FlutterMethodNotImplemented); } diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 6c6d90b9bcee..6c6214e96951 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -11,5 +11,6 @@ export 'package:camera_platform_interface/camera_platform_interface.dart' CameraDescription, CameraException, CameraLensDirection, + FlashMode, ResolutionPreset, XFile; From b68cc80e2d646007b1ae3ece822f0bb1d7cda44e Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 15 Dec 2020 14:33:35 +0100 Subject: [PATCH 86/89] Add reqeusted unit tests --- packages/camera/camera/test/camera_test.dart | 43 ++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index b129849cd141..cd67e6fb96c1 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -310,6 +310,43 @@ void main() { 'startVideoRecording was called while a camera was streaming images.', ))); }); + + test('setFlashMode() calls $CameraPlatform', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.setFlashMode(FlashMode.always); + + verify(CameraPlatform.instance + .setFlashMode(cameraController.cameraId, FlashMode.always)) + .called(1); + }); + + test('setFlashMode() throws $CameraException on $PlatformException', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + mockPlatformException = true; + expect( + cameraController.setFlashMode(FlashMode.always), + throwsA(isA().having( + (error) => error.description, + 'foo', + 'bar', + ))); + mockPlatformException = false; + }); }); } @@ -330,6 +367,12 @@ class MockCameraPlatform extends Mock ? throw PlatformException(code: 'foo', message: 'bar') : Future.value(mockInitializeCamera); + @override + Future setFlashMode(int cameraId, FlashMode mode) => + mockPlatformException + ? throw PlatformException(code: 'foo', message: 'bar') + : noSuchMethod(Invocation.method(#setFlashMode, [cameraId, mode])); + @override Stream onCameraInitialized(int cameraId) => Stream.value(mockOnCameraInitializedEvent); From 0c7f391023a1e87c3b049af735b7254dcfbef2d9 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 15 Dec 2020 14:43:18 +0100 Subject: [PATCH 87/89] Clean up new tests --- packages/camera/camera/test/camera_test.dart | 22 +++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index cd67e6fb96c1..c19aa9718f47 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -337,15 +337,23 @@ void main() { ResolutionPreset.max); await cameraController.initialize(); - mockPlatformException = true; + when(CameraPlatform.instance + .setFlashMode(cameraController.cameraId, FlashMode.always)) + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + expect( cameraController.setFlashMode(FlashMode.always), throwsA(isA().having( (error) => error.description, - 'foo', - 'bar', + 'TEST_ERROR', + 'This is a test error message', ))); - mockPlatformException = false; }); }); } @@ -367,12 +375,6 @@ class MockCameraPlatform extends Mock ? throw PlatformException(code: 'foo', message: 'bar') : Future.value(mockInitializeCamera); - @override - Future setFlashMode(int cameraId, FlashMode mode) => - mockPlatformException - ? throw PlatformException(code: 'foo', message: 'bar') - : noSuchMethod(Invocation.method(#setFlashMode, [cameraId, mode])); - @override Stream onCameraInitialized(int cameraId) => Stream.value(mockOnCameraInitializedEvent); From 04cca3950173f384408125b18d9529b13b60e758 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 17 Dec 2020 10:25:10 +0100 Subject: [PATCH 88/89] Add unit tests for Android implementation --- .../camera/PictureCaptureRequestTest.java | 93 +++++++++++++++++++ .../plugins/camera/types/FlashModeTest.java | 26 ++++++ 2 files changed, 119 insertions(+) create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java new file mode 100644 index 000000000000..2b6aa0f25fcf --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java @@ -0,0 +1,93 @@ +package io.flutter.plugins.camera; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import io.flutter.plugin.common.MethodChannel; +import org.junit.Test; + +public class PictureCaptureRequestTest { + + @Test + public void state_is_idle_by_default() { + PictureCaptureRequest req = new PictureCaptureRequest(null); + assertEquals("Default state is idle", req.getState(), PictureCaptureRequest.State.idle); + } + + @Test + public void setState_sets_state() { + PictureCaptureRequest req = new PictureCaptureRequest(null); + req.setState(PictureCaptureRequest.State.awaitingPreCapture); + assertEquals( + "State is awaitingPreCapture", + req.getState(), + PictureCaptureRequest.State.awaitingPreCapture); + req.setState(PictureCaptureRequest.State.capturing); + assertEquals( + "State is awaitingPreCapture", req.getState(), PictureCaptureRequest.State.capturing); + } + + @Test + public void finish_sets_result_and_state() { + // Setup + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + PictureCaptureRequest req = new PictureCaptureRequest(mockResult); + // Act + req.finish("/test/path"); + // Test + verify(mockResult).success("/test/path"); + assertEquals("State is finished", req.getState(), PictureCaptureRequest.State.finished); + } + + @Test + public void isFinished_is_true_When_state_is_finished_or_error() { + // Setup + PictureCaptureRequest req = new PictureCaptureRequest(null); + // Test false states + req.setState(PictureCaptureRequest.State.idle); + assertFalse(req.isFinished()); + req.setState(PictureCaptureRequest.State.awaitingPreCapture); + assertFalse(req.isFinished()); + req.setState(PictureCaptureRequest.State.capturing); + assertFalse(req.isFinished()); + // Test true states + req.setState(PictureCaptureRequest.State.finished); + assertTrue(req.isFinished()); + req = new PictureCaptureRequest(null); // Refresh + req.setState(PictureCaptureRequest.State.error); + assertTrue(req.isFinished()); + } + + @Test(expected = IllegalStateException.class) + public void finish_throws_When_already_finished() { + // Setup + PictureCaptureRequest req = new PictureCaptureRequest(null); + req.setState(PictureCaptureRequest.State.finished); + // Act + req.finish("/test/path"); + } + + @Test + public void error_sets_result_and_state() { + // Setup + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + PictureCaptureRequest req = new PictureCaptureRequest(mockResult); + // Act + req.error("ERROR_CODE", "Error Message", null); + // Test + verify(mockResult).error("ERROR_CODE", "Error Message", null); + assertEquals("State is error", req.getState(), PictureCaptureRequest.State.error); + } + + @Test(expected = IllegalStateException.class) + public void error_throws_When_already_finished() { + // Setup + PictureCaptureRequest req = new PictureCaptureRequest(null); + req.setState(PictureCaptureRequest.State.finished); + // Act + req.error(null, null, null); + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java new file mode 100644 index 000000000000..0549e4fc750e --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java @@ -0,0 +1,26 @@ +package io.flutter.plugins.camera.types; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class FlashModeTest { + + @Test + public void getValueForString_returns_correct_values() { + assertEquals( + "Returns FlashMode.off for 'off'", FlashMode.getValueForString("off"), FlashMode.off); + assertEquals( + "Returns FlashMode.auto for 'auto'", FlashMode.getValueForString("auto"), FlashMode.auto); + assertEquals( + "Returns FlashMode.always for 'always'", + FlashMode.getValueForString("always"), + FlashMode.always); + } + + @Test + public void getValueForString_returns_null_for_nonexistant_value() { + assertEquals( + "Returns null for 'nonexistant'", FlashMode.getValueForString("nonexistant"), null); + } +} From 97ed5d9e2db1a3a52148a3226635af9ccf15926d Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 17 Dec 2020 10:31:16 +0100 Subject: [PATCH 89/89] Update platform interface dependency to point to pub.dev release. --- packages/camera/camera/pubspec.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 3c152275b580..3194fff33684 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -8,10 +8,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: flutter: sdk: flutter - - # TODO(BeMacized): Update dependency to pub.dev version once updated camera_platform_interface is published - camera_platform_interface: - path: ../camera_platform_interface + camera_platform_interface: ^1.0.4 dev_dependencies: path_provider: ^0.5.0