Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[camera] Add implementations for camera_platform_interface package. #3302

Merged
merged 67 commits into from
Dec 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
76787aa
First suggestion for camera platform interface
mvanbeusekom Nov 6, 2020
d0be649
Remove test coverage folder
mvanbeusekom Nov 6, 2020
7b4c8be
Renamed onLatestImageAvailableHandler definition
mvanbeusekom Nov 9, 2020
a959bbe
Split CameraEvents into separate streams
mvanbeusekom Nov 12, 2020
2fa0419
Implemented & tested first parts of method channel implementation
BeMacized Nov 13, 2020
ef6dee8
Remove unused EventChannelMock class
BeMacized Nov 13, 2020
c15df6c
Add missing unit tests
BeMacized Nov 13, 2020
8e33d0e
Added placeholders in default method channel implementation
BeMacized Nov 13, 2020
a9ea2b9
Updated platform interface
BeMacized Nov 13, 2020
62c9a8c
Update packages/camera/camera_platform_interface/lib/src/platform_int…
BeMacized Nov 13, 2020
ca7f5d7
Add unit test for availableCameras
BeMacized Nov 13, 2020
eff917f
Expand availableCameras unit test. Added unit test for takePicture.
BeMacized Nov 13, 2020
afd02f7
Add unit test for startVideoRecording
BeMacized Nov 13, 2020
397acce
Add unit test for prepareForVideoRecording
BeMacized Nov 13, 2020
39b4cd5
Add unit test for stopVideoRecording
BeMacized Nov 13, 2020
a999954
Add unit test for pauseVideoRecording
BeMacized Nov 13, 2020
451500a
Add unit test for buildView
BeMacized Nov 13, 2020
5bf7795
WIP: Dart and Android implementation
danielroek Nov 19, 2020
78b4db0
Fix formatting
mvanbeusekom Nov 13, 2020
8afa33a
Have resolution stream replay last value on subscription. Replace str…
BeMacized Nov 19, 2020
d3411ba
Added reverse method channel to replace event channel. Updated initia…
BeMacized Nov 19, 2020
a96f785
Fixed example app for Android. Removed isRecordingVideo and isStreami…
danielroek Nov 19, 2020
28a3662
Added some first tests for camera/camera
danielroek Nov 23, 2020
2ea1a00
More tests and some feedback
danielroek Nov 26, 2020
484f1ce
iOS implementation: Removed standard event channel. Added reverse met…
BeMacized Nov 20, 2020
8571665
Started splitting initialize method
mvanbeusekom Nov 26, 2020
f83d6b5
Finish splitting up initialize for iOS
BeMacized Nov 26, 2020
99b36eb
Update unit tests
BeMacized Nov 26, 2020
53ad8a1
Fix takePicture method on iOS
BeMacized Nov 26, 2020
eadc398
Split initialize method on Android
mvanbeusekom Nov 27, 2020
752e764
Fix video recording on iOS. Updated platform interface.
BeMacized Nov 27, 2020
1b74948
Update unit tests
BeMacized Nov 27, 2020
fe00999
Update error handling of video methods in iOS code. Make iOS code mor…
BeMacized Nov 27, 2020
8c3cc3b
Updated startVideoRecording documentation
mvanbeusekom Nov 27, 2020
a6441f4
Make sure file is returned by stopVideoRecording
mvanbeusekom Nov 27, 2020
959fd3e
Use correct event-type after initializing
mvanbeusekom Nov 27, 2020
b8eeb05
Fix DartMessenger unit-tests
mvanbeusekom Nov 27, 2020
e34caa4
Change cast
BeMacized Nov 27, 2020
f1fb5f0
Fix formatting
mvanbeusekom Nov 27, 2020
d1f6cd1
Fixed tests, formatting and analysis warnings
mvanbeusekom Nov 27, 2020
6a30ccd
Added missing license to Dart files
mvanbeusekom Nov 30, 2020
b26d633
Updated CHANGELOG and version
mvanbeusekom Nov 30, 2020
9055fe1
Added additional unit-tests to platform_interface
mvanbeusekom Nov 30, 2020
e8c2977
Added more tests
danielroek Nov 30, 2020
086c3ed
Formatted code
danielroek Nov 30, 2020
78fac3a
Re-added the CameraPreview widget
mvanbeusekom Dec 2, 2020
ca13bd5
Use import/export instead of part implementation
mvanbeusekom Dec 3, 2020
6fcd5e7
fixed formatting
mvanbeusekom Dec 3, 2020
b442759
Resolved additional feedback
mvanbeusekom Dec 3, 2020
def916f
Update dependency to git repo
mvanbeusekom Dec 4, 2020
1d1365a
Depend on pub.dev for camera_platform_interface
mvanbeusekom Dec 8, 2020
fb14d0c
Fix JAVA formatting
mvanbeusekom Dec 8, 2020
69fa625
Fix changelog
mvanbeusekom Dec 9, 2020
5bda95c
Make sure camera package can be published
mvanbeusekom Dec 9, 2020
a063f88
Assert when stream methods are called from wrong platform
mvanbeusekom Dec 9, 2020
fee08ba
Add dev_dependency on plugin_platform_interface package, required by …
ditman Dec 10, 2020
ed92b75
Remove pedantic requirement from initialize() method. Remove unnecess…
ditman Dec 10, 2020
844d321
Remove dependency on dart:io
mvanbeusekom Dec 10, 2020
381450d
Restrict exposed types from platform interface
mvanbeusekom Dec 11, 2020
bfc6b92
Moved test for image stream in separate file
mvanbeusekom Dec 11, 2020
a0b4dfa
Fixed formatting issue
mvanbeusekom Dec 11, 2020
ca6052a
Fix deprecation warning
mvanbeusekom Dec 11, 2020
ef867ba
Apply feedback from bparrishMines
mvanbeusekom Dec 11, 2020
3e24d9f
Fix formatting issues
mvanbeusekom Dec 12, 2020
46a1b27
Removed redundant podspec files
mvanbeusekom Dec 12, 2020
7f266cd
Removed redundant ios files
mvanbeusekom Dec 12, 2020
c1c04a6
Handle SecurityException
mvanbeusekom Dec 12, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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 is now used to implement the CameraPreview widget.

## 0.5.8+19

* Update Flutter SDK constraint.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ 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;

// Mirrors camera.dart
public enum ResolutionPreset {
Expand Down Expand Up @@ -94,6 +96,7 @@ public Camera(
this.flutterTexture = flutterTexture;
this.dartMessenger = dartMessenger;
this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
this.applicationContext = activity.getApplicationContext();
orientationEventListener =
new OrientationEventListener(activity.getApplicationContext()) {
@Override
Expand Down Expand Up @@ -135,7 +138,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);
Expand All @@ -154,15 +157,13 @@ public void onOpened(@NonNull CameraDevice device) {
try {
startPreview();
} catch (CameraAccessException e) {
result.error("CameraAccess", e.getMessage(), null);
dartMessenger.sendCameraErrorEvent(e.getMessage());
close();
return;
}
Map<String, Object> reply = new HashMap<>();
reply.put("textureId", flutterTexture.id());
reply.put("previewWidth", previewSize.getWidth());
reply.put("previewHeight", previewSize.getHeight());
result.success(reply);

dartMessenger.sendCameraInitializedEvent(
previewSize.getWidth(), previewSize.getHeight());
}

@Override
Expand All @@ -174,7 +175,7 @@ public void onClosed(@NonNull CameraDevice camera) {
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
close();
dartMessenger.send(DartMessenger.EventType.ERROR, "The camera was disconnected.");
dartMessenger.sendCameraErrorEvent("The camera was disconnected.");
}

@Override
Expand All @@ -200,7 +201,7 @@ public void onError(@NonNull CameraDevice cameraDevice, int errorCode) {
default:
errorDescription = "Unknown camera error";
}
dartMessenger.send(DartMessenger.EventType.ERROR, errorDescription);
dartMessenger.sendCameraErrorEvent(errorDescription);
}
},
null);
Expand All @@ -218,12 +219,13 @@ SurfaceTextureEntry getFlutterTexture() {
return flutterTexture;
}

public void takePicture(String filePath, @NonNull final Result result) {
final File file = new File(filePath);

if (file.exists()) {
result.error(
"fileExists", "File at path '" + filePath + "' already exists. Cannot overwrite.", null);
public void takePicture(@NonNull final Result result) {
final File outputDir = applicationContext.getCacheDir();
final File file;
try {
file = File.createTempFile("CAP", ".jpg", outputDir);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we handle SecurityException too ?
I see we have static file name for pictures. We can save one picture?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the case, the createTempFile method will create a unique file name. The first parameter ("CAP" in this case) is a prefix that will be used to generate the name (see [File.createTempFile](https://docs.oracle.com/javase/7/docs/api/java/io/File.html#createTempFile(java.lang.String,%20java.lang.String,%20java.io.File) for details).

The actual file will look something like "CAP3602253894598046604.jpg"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch regarding the SecurityException, I will add support to handle this as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great

} catch (IOException | SecurityException e) {
result.error("cannotCreateFile", e.getMessage(), null);
return;
}

Expand All @@ -232,7 +234,7 @@ public void takePicture(String filePath, @NonNull final Result result) {
try (Image image = reader.acquireLatestImage()) {
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
writeToFile(buffer, file);
result.success(null);
result.success(file.getAbsolutePath());
} catch (IOException e) {
result.error("IOError", "Failed saving image", null);
}
Expand Down Expand Up @@ -308,8 +310,7 @@ private void createCaptureSession(
public void onConfigured(@NonNull CameraCaptureSession session) {
try {
if (cameraDevice == null) {
dartMessenger.send(
DartMessenger.EventType.ERROR, "The camera was closed during configuration.");
dartMessenger.sendCameraErrorEvent("The camera was closed during configuration.");
return;
}
cameraCaptureSession = session;
Expand All @@ -320,14 +321,13 @@ public void onConfigured(@NonNull CameraCaptureSession session) {
onSuccessCallback.run();
}
} catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) {
dartMessenger.send(DartMessenger.EventType.ERROR, e.getMessage());
dartMessenger.sendCameraErrorEvent(e.getMessage());
}
}

@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
dartMessenger.send(
DartMessenger.EventType.ERROR, "Failed to configure camera session.");
dartMessenger.sendCameraErrorEvent("Failed to configure camera session.");
}
};

Expand Down Expand Up @@ -369,18 +369,24 @@ private void createCaptureSession(
cameraDevice.createCaptureSession(surfaces, callback, null);
}

public void startVideoRecording(String filePath, Result result) {
if (new File(filePath).exists()) {
result.error("fileExists", "File at path '" + filePath + "' already exists.", null);
public void startVideoRecording(Result result) {
final File outputDir = applicationContext.getCacheDir();
try {
videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir);
} catch (IOException | SecurityException e) {
result.error("cannotCreateFile", e.getMessage(), null);
return;
}

try {
prepareMediaRecorder(filePath);
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);
}
}
Expand All @@ -396,7 +402,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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,56 @@
import android.text.TextUtils;
import androidx.annotation.Nullable;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel;
import java.util.HashMap;
import java.util.Map;

class DartMessenger {
@Nullable private EventChannel.EventSink eventSink;
@Nullable private MethodChannel channel;

enum EventType {
ERROR,
CAMERA_CLOSING,
INITIALIZED,
}

DartMessenger(BinaryMessenger messenger, long eventChannelId) {
new EventChannel(messenger, "flutter.io/cameraPlugin/cameraEvents" + eventChannelId)
.setStreamHandler(
new EventChannel.StreamHandler() {
@Override
public void onListen(Object arguments, EventChannel.EventSink sink) {
eventSink = sink;
}

@Override
public void onCancel(Object arguments) {
eventSink = null;
}
});
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<String, Object>() {
{
if (previewWidth != null) put("previewWidth", previewWidth.doubleValue());
if (previewHeight != null) put("previewHeight", previewHeight.doubleValue());
}
});
}

void sendCameraClosingEvent() {
send(EventType.CAMERA_CLOSING, null);
send(EventType.CAMERA_CLOSING);
}

void send(EventType eventType, @Nullable String description) {
if (eventSink == null) {
return;
}
void sendCameraErrorEvent(@Nullable String description) {
this.send(
EventType.ERROR,
new HashMap<String, Object>() {
{
if (!TextUtils.isEmpty(description)) put("description", description);
}
});
}

void send(EventType eventType) {
send(eventType, new HashMap<>());
}

Map<String, String> event = new HashMap<>();
event.put("eventType", eventType.toString().toLowerCase());
// Only errors have a description.
if (eventType == EventType.ERROR && !TextUtils.isEmpty(description)) {
event.put("errorDescription", description);
void send(EventType eventType, Map<String, Object> args) {
if (channel == null) {
return;
}
eventSink.success(event);
channel.invokeMethod(eventType.toString().toLowerCase(), args);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -69,12 +72,28 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
result.error(errCode, errDesc, null);
}
});

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(call.argument("path"), result);
camera.takePicture(result);
break;
}
case "prepareForVideoRecording":
Expand All @@ -85,7 +104,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":
Expand Down Expand Up @@ -157,7 +176,9 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce
resolutionPreset,
enableAudio);

camera.open(result);
Map<String, Object> reply = new HashMap<>();
reply.put("cameraId", flutterSurfaceTexture.id());
result.success(reply);
}

// We move catching CameraAccessException out of onMethodCall because it causes a crash
Expand Down
Loading