Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[camerax] Implement video capture for CameraX android camera re-write #3467

Merged
merged 19 commits into from
May 9, 2023

Conversation

gmackall
Copy link
Member

@gmackall gmackall commented Mar 15, 2023

This PR implements video capture for the CameraX re-write of the android camera plugin.

flutter/flutter#111138

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the relevant style guides and ran the auto-formatter. (Unlike the flutter/flutter repo, the flutter/packages repo does use dart format.)
  • I signed the CLA.
  • The title of the PR starts with the name of the package surrounded by square brackets, e.g. [shared_preferences]
  • I listed at least one issue that this PR fixes in the description above.
  • I updated pubspec.yaml with an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.
  • I updated CHANGELOG.md to add a description of the change, following repository CHANGELOG style.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@gmackall gmackall requested a review from camsim99 as a code owner March 15, 2023 19:01
@google-cla

This comment was marked as resolved.

@gmackall gmackall changed the title Video capture Implement video capture for CameraX android camera re-write Mar 15, 2023
@gmackall gmackall changed the title Implement video capture for CameraX android camera re-write [camera] Implement video capture for CameraX android camera re-write Mar 15, 2023
packages/camera/camera_android_camerax/CHANGELOG.md Outdated Show resolved Hide resolved
recordingFlutterApi = new RecordingFlutterApiImpl(binaryMessenger, instanceManager);
}

public void setContext(Context context) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add javadoc explaining why this is necessary?

Copy link
Contributor

Choose a reason for hiding this comment

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

For all public methods, I suggest we do this. Private is optional but could be useful for methods that are not a direct wrapping of whatever call to CameraX.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is there guidance about if this is an application context or an activity context? My guess is activity.

@Override
public Long start(@NonNull Long identifier) {
PendingRecording pendingRecording = getPendingRecordingFromInstanceId(identifier);
Recording recording = pendingRecording.start(this.getExecutor(), this::handleVideoRecordEvent);
Copy link
Contributor

Choose a reason for hiding this comment

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

:: was introduced in Java 8 and we have to stick to things available in Java 7 to keep things consistent with the internal version of this plugin.

this.instanceManager = instanceManager;
}

void create(PendingRecording pendingRecording, Reply<Void> reply) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: add nullable/nonnull annotations here

}

void create(
Recorder recorder, @Nullable Long aspectRatio, @Nullable Long bitRate, Reply<Void> reply) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: add @NonNull

Copy link
Contributor

Choose a reason for hiding this comment

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

I see some other missing ones so just reminder to add them throughout the Java files!

@@ -30,6 +30,9 @@ class SystemServices {
static final StreamController<String> cameraErrorStreamController =
StreamController<String>.broadcast();

static final StreamController<CameraEvent> cameraEventStreamController =
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove this?

when(mockRecorderBuilder.setExecutor(any(Executor.class))).thenReturn(mockRecorderBuilder);
when(mockRecorderBuilder.build()).thenReturn(mockRecorder);

recorderHostApi.create(0L, 1L, 2L);
Copy link
Contributor

Choose a reason for hiding this comment

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

For constants that matter in testing, we opted for defining constants and just converting inline between int and Long.

@gmackall gmackall requested a review from reidbaker March 21, 2023 16:05
cameraController == null ? null :
(cameraController.value.isRecordingPaused
? onResumeButtonPressed : onPauseButtonPressed),
onPressed: cameraController == null
Copy link
Contributor

Choose a reason for hiding this comment

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

Nested ternary operators can be difficult to read. Can you break this apart?

@@ -61,6 +61,14 @@ class SystemServices {

api.stopListeningForDeviceOrientationChange();
}

/// Gets the current cache directory path from context.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you expand a bit on this documentation? For example what would change the current cache directory, who else uses the current cache directory, are there constraints on its use or the files that can be put in it.

Copy link
Contributor

@camsim99 camsim99 left a comment

Choose a reason for hiding this comment

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

Looking good! I left a comment on one main concern about using aspect ratio instead of resolution (I think we actually can use resolution), but otherwise, just requests for some tests and nits.

@@ -20,6 +20,9 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
private ProcessCameraProviderHostApiImpl processCameraProviderHostApi;
private ImageCaptureHostApiImpl imageCaptureHostApi;
public SystemServicesHostApiImpl systemServicesHostApi;
private PendingRecordingHostApiImpl pendingRecordingHostApi;
private RecorderHostApiImpl recorderHostApi;
private VideoCaptureHostApiImpl videoCaptureHostApi;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I decided to rename these to ApiImpl in #3419. Can you change these to keep it consistent?

}

void create(@NonNull PendingRecording pendingRecording, @Nullable Reply<Void> reply) {
create(instanceManager.addHostCreatedInstance(pendingRecording), reply);
Copy link
Contributor

Choose a reason for hiding this comment

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

Side note: I think I'll file an issue for general cleanup of this plugin, including making sure we are consistent about checking whether or not an instance has been added to the instance manager before creating it in the FlutterApiImpl or where it's used in in a HostApiImpl. I know with the generated wrapped classes, it checks in the former, but we both have done it in the latter.

Copy link
Contributor

Choose a reason for hiding this comment

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

public Long start(@NonNull Long identifier) {
PendingRecording pendingRecording = getPendingRecordingFromInstanceId(identifier);
Recording recording =
pendingRecording.start(this.getExecutor(), x -> handleVideoRecordEvent(x));
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: can you rename x to something like event for clarity?

return ContextCompat.getMainExecutor(context);
}

private void handleVideoRecordEvent(VideoRecordEvent event) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: add javadoc

Comment on lines 73 to 74
return instanceManager.getInstanceWithWeakReference(videoCaptureId)!
as VideoCapture;
Copy link
Contributor

Choose a reason for hiding this comment

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

I have received lint errors by using this instead of:

Suggested change
return instanceManager.getInstanceWithWeakReference(videoCaptureId)!
as VideoCapture;
return instanceManager.getInstanceWithWeakReference<VideoCapture>(videoCaptureId)!;

when(mockApi.start(pendingRecordingId)).thenReturn(mockRecordingId);
expect(await pendingRecording.start(), mockRecording);
verify(mockApi.start(pendingRecordingId));
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you also add a test for the Flutter API create method?

Copy link
Contributor

Choose a reason for hiding this comment

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

@camsim99 can you resolve this if you are happy with the test added?

Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: can you make the test title more specific? Otherwise, lgtm

@gmackall gmackall changed the title [camera] Implement video capture for CameraX android camera re-write [camerax] Implement video capture for CameraX android camera re-write May 1, 2023
@gmackall gmackall requested a review from camsim99 May 1, 2023 23:42
Copy link
Contributor

@camsim99 camsim99 left a comment

Choose a reason for hiding this comment

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

LGTM!

public void create(@NonNull Long instanceId, @Nullable Long aspectRatio, @Nullable Long bitRate) {
Recorder.Builder recorderBuilder = cameraXProxy.createRecorderBuilder();
if (aspectRatio != null) {
recorderBuilder.setAspectRatio(aspectRatio.intValue());
Copy link
Contributor

Choose a reason for hiding this comment

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

We talked offline and decided to implement this in a later PR. This will fall under flutter/flutter#120462

final Long mockRecordingId = testInstanceManager.addHostCreatedInstance(mockRecording);
testInstanceManager.addDartCreatedInstance(mockPendingRecording, mockPendingRecordingId);

doReturn(mockRecording).when(mockPendingRecording).start(any(), any());
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks good!

@gmackall gmackall requested a review from reidbaker May 2, 2023 16:39
Copy link
Contributor

@reidbaker reidbaker left a comment

Choose a reason for hiding this comment

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

partial review.

@@ -161,6 +163,60 @@ abstract class PreviewHostApi {
ResolutionInfo getResolutionInfo(int identifier);
}

@HostApi(dartHostTestHandler: 'TestVideoCaptureHostApi')
abstract class VideoCaptureHostApi {
int withOutput(int videoOutputId);
Copy link
Contributor

Choose a reason for hiding this comment

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

non blocking: is there a reason there are no dart docs on any of these objects?

Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW, he's following a pattern I started. We should consider copying the documentation from the wrapped classes to these objects in a future cleanup PR.

{BinaryMessenger? binaryMessenger, InstanceManager? instanceManager})
: super.detached(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager) {
Copy link
Contributor

Choose a reason for hiding this comment

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

In many of @camsim99 prs I see null instance managers overridden by globals.instanceManger is that something that should happen here. If not why not?

Copy link
Contributor

Choose a reason for hiding this comment

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

I missed this -- we should do this here too!

Copy link
Member Author

Choose a reason for hiding this comment

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

The instance manager here in VideoCapture is only being used in the constructor of the VideoCaptureHostApiImpl, and in that constructor we override to the global instance manager. This is the same pattern that is being used elsewhere, for examples see the Preview constructor vs the PreviewHostApiImpl constructor.

Copy link
Contributor

Choose a reason for hiding this comment

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

@camsim99 or @gmackall can one of you add to your growing clean up bug to make this pattern consistent. If we cant keep it straight then neither can first time contributors.

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe the pattern here is consistent.

In the Thing constructor, we pass in an InstanceManager? instanceManager, and then use that to create a ThingHostApiImpl, and then in the ThingHostApiImpl constructor we check if that instanceManager we passed is null and override to the global if so.

Copy link
Member Author

@gmackall gmackall May 8, 2023

Choose a reason for hiding this comment

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

Actually, I see that some of the newer wrapped classes like zoom state and exposure state do not create these HostApiImpls in their detached constructors. I'll file an issue to ensure the detached versions of the constructors all align on whether to create or not (probably not, given the point is that they are not attached to a native object and therefore do not need to call host api methods).

Copy link
Member Author

Choose a reason for hiding this comment

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

AndroidCameraXCameraFlutterApis.instance.ensureSetUp();
}

/// Creates a VideoCapture associated with the given [Recorder].
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Creates a VideoCapture associated with the given [Recorder].
/// Creates a [VideoCapture] associated with the given [Recorder].

@@ -471,6 +484,83 @@ void main() {
expect(previewTexture.textureId, equals(textureId));
});

test(
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense make these tests share a group?

Copy link
Member Author

Choose a reason for hiding this comment

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

Made them share a group in the new PR

verifyNoMoreInteractions(recording);
});

test('resumeVideoRecording resumes the recording', () async {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this test not need to pause the recording first? If not would that be a valuable test?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because this dart unit test is using a MockRecording, and the dart Recording class (and therefore the mock recording object) doesn't on its own have any tracking of an internal state without a real corresponding native Recording, it didn't seem to me like it mattered here.

Does that make sense?

Copy link
Contributor

Choose a reason for hiding this comment

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

Are there error cases around recording that should be tested. For example calling stop twice in a row?

Copy link
Contributor

Choose a reason for hiding this comment

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

This sort of testing is good, and likely the sort we should use for several other methods tested here. Not sure this should block this PR, though, so if you don't get to it, please add it here: flutter/flutter#125928

Copy link
Contributor

@reidbaker reidbaker May 8, 2023

Choose a reason for hiding this comment

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

blocking: I do think that some error testing should be required before merging even if the test is that we we swallow the error and don't crash (or that we crash and the crash is part of the documented behavior)

Copy link
Member Author

@gmackall gmackall May 8, 2023

Choose a reason for hiding this comment

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

For example calling stop twice in a row?

This action currently throws a CameraException. I'll add a test for it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added a test for

  1. if the recording is null (we haven't started a recording). In this case we throw a CameraException
  2. if the videoOutputPath is null (we shouldn't generally reach this case). In this case we throw a CameraException
  3. We call stop on a valid recording, and then we call it again (in this case we test everything performs normally for the first call to stop, and then we throw a CameraException on the second call).

Copy link
Member Author

Choose a reason for hiding this comment

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

Also added a logic and a test for calling recording twice (we silently do nothing in this case, based on a check of if the class variable recording is null or not).


/// Closes the specified recording instance.
Future<void> closeFromInstance(Recording recording) async {
close(instanceManager.getIdentifier(recording)!);
Copy link
Contributor

Choose a reason for hiding this comment

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

I dont think we should use a bang here or in the methods below. If a recording call fails I wouldn't expect an app crash and I don't see anything in the documentation about catching null pointer exceptions if we cant find an identifier.

Copy link
Member Author

Choose a reason for hiding this comment

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

Is this not the same behavior as in other usecases? See takePictureFromInstance for example. My understanding is that the assert statements only have effect in a debugging context, but in release use this would behave in the same way with an app crash?

Is that understanding correct/should we introduce a common error type or pattern here to replace the bang usages?

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree it may be a useful tech debt item to include a more helpful error message, but hitting this means most likely someone adding new wrapped classes made an error or maybe someone modifying the plugin implementation did. It won't crash the app, either, so leaving it like this should be ok for now.

@reidbaker
Copy link
Contributor

before continuing my pass I need the cla to be figured out.

@gmackall

This comment was marked as outdated.

Copy link
Contributor

@reidbaker reidbaker left a comment

Choose a reason for hiding this comment

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

Final approval is up for @camsim99 to decide. I know there is a balance to quality and speed we are trying to hit.

/** Returns a path to be used to create a temp file in the current cache directory. */
@Override
@NonNull
public String getTempFilePath(@NonNull String prefix, @NonNull String suffix) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: filePrefux and fileSuffix, as opposed to path or some other definition. Also acceptable would be updating the documentation.
you also might consider making these named parameters for call site readability.

try {
File path = File.createTempFile(prefix, suffix, context.getCacheDir());
return path.toString();
} catch (IOException | SecurityException e) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If you know when these might be expected consider adding a comment.

File path = File.createTempFile(prefix, suffix, context.getCacheDir());
return path.toString();
} catch (IOException | SecurityException e) {
throw new RuntimeException(e);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be a typed exception?

Copy link
Member Author

Choose a reason for hiding this comment

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

At the time it was written, it was only possible to use RuntimeException here (the general pattern was that this method is wrapped in a try catch anyways in GeneratedCameraXLibrary, and then GeneratedCameraXLibrary would throw a PlatformException).

Since updating to pigeon 9.10, we can throw FlutterError instead (there is some discussion about the old pattern vs new FlutterError on this old PR). I will change here to throw a FlutterError.

this.instanceManager = instanceManager;
}

@Override
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: consider ordering these methods in the order they will likely be called or into related pairs.

/// File.createTempFile(prefix, suffix, cacheDir), on the android side, where
/// where cacheDir is the cache directory identified by the current application
/// context using context.getCacheDir().
static Future<String> getTempFilePath(String prefix, String suffix,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think from the tests I read prefix is actually the file name and suffix is the extension. That came as a bit of a suprise from reading the documentation I would have expected prefix<something the caller does not control>suffix for the file name.

For example it means as a caller I think this means I need to ensure that I pass something different each time or risk overwriting the previous file.

Copy link
Member Author

@gmackall gmackall May 8, 2023

Choose a reason for hiding this comment

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

I'll clarify the documentation before merging, the way it actually works is that the file ends up being
prefix<something the caller does not control>.suffix, i.e. 'suffix' is the file extension, and prefix is part of the file name, but more gets added by createTempFile (a random sequence of numbers).

So getTempFile('MOV', '.temp'), is something like <cachedir>/MOV1231242244.temp (notably this means you do not have to pass different things to avoid overwriting).

FWIW I chose prefix and suffix because those are the variable names used by the native methods implementation/documentation.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK in that case it means the documentation is correct but that is not what the tests are testing for. I think using the same names as the native code is a reasonable choice.

Specifically in packages/camera/camera_android_camerax/test/system_services_test.dart and in packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java you are testing for the result to be equal totestPath + testPrefix + testSuffix (or the file for prefix and suffix). Instead of testing that the output had a path of testPath a file prefix of testPrefix and an extension of testSuffix. Which is fine it is just confusing when trying to understand what to expect from the tests.

when(mockApi.start(pendingRecordingId)).thenReturn(mockRecordingId);
expect(await pendingRecording.start(), mockRecording);
verify(mockApi.start(pendingRecordingId));
});
Copy link
Contributor

Choose a reason for hiding this comment

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

@camsim99 can you resolve this if you are happy with the test added?

@reidbaker reidbaker requested a review from camsim99 May 4, 2023 19:55
Copy link
Contributor

@camsim99 camsim99 left a comment

Choose a reason for hiding this comment

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

Approving to unblock this. I left comments on things I think can be done later, but otherwise agree with the rest of the review comments left!

}

void create(@NonNull PendingRecording pendingRecording, @Nullable Reply<Void> reply) {
create(instanceManager.addHostCreatedInstance(pendingRecording), reply);
Copy link
Contributor

Choose a reason for hiding this comment

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


/// Closes the specified recording instance.
Future<void> closeFromInstance(Recording recording) async {
close(instanceManager.getIdentifier(recording)!);
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree it may be a useful tech debt item to include a more helpful error message, but hitting this means most likely someone adding new wrapped classes made an error or maybe someone modifying the plugin implementation did. It won't crash the app, either, so leaving it like this should be ok for now.

@@ -161,6 +163,60 @@ abstract class PreviewHostApi {
ResolutionInfo getResolutionInfo(int identifier);
}

@HostApi(dartHostTestHandler: 'TestVideoCaptureHostApi')
abstract class VideoCaptureHostApi {
int withOutput(int videoOutputId);
Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW, he's following a pattern I started. We should consider copying the documentation from the wrapped classes to these objects in a future cleanup PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

This sort of testing is good, and likely the sort we should use for several other methods tested here. Not sure this should block this PR, though, so if you don't get to it, please add it here: flutter/flutter#125928

when(mockApi.start(pendingRecordingId)).thenReturn(mockRecordingId);
expect(await pendingRecording.start(), mockRecording);
verify(mockApi.start(pendingRecordingId));
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: can you make the test title more specific? Otherwise, lgtm

Gray Mackall added 2 commits May 8, 2023 14:46
…ng test to cover case. Also change to null videoOutputPath error case and test
@gmackall
Copy link
Member Author

gmackall commented May 8, 2023

To make reviewing easier (because this has been updated a lot), since your last review @reidbaker the summary of changes is:

  1. Added logic in startVideoRecording to explicitly do nothing if recording is not null (there is an active recording).
  2. Added a test for this case where we call startVideoRecording twice, and verify the methods we expect to be called only once are indeed called only once (covering the above case).
  3. Made a change to the stopVideoRecording error case where the videoOutputPath is null so that we clean up the recording objects (stop the recording, set recording and pendingRecording to null) because we can't recover from this case without starting a new recording as videoOutputPath is set in the startVideoRecording. This way change (1) doesn't get us stuck forever in the null videoOutputPath case.
  4. Added a test to cover the above.

@@ -421,6 +421,11 @@ class AndroidCameraCameraX extends CameraPlatform {
assert(cameraSelector != null);
assert(processCameraProvider != null);

if (recording != null) {
// There is currently an active recording, so do not start a new one.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a statement to the doc for this method that this is the expected behavior for trying to start a new recording?

Copy link
Member Author

Choose a reason for hiding this comment

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

Added a note that we silently return without doing anything if there is an active recording.

// in this error case.
recording!.close();
recording = null;
pendingRecording = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here about adding it to the doc

Copy link
Member Author

Choose a reason for hiding this comment

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

Added a note on the cleanup of the recording objects in the doc

camera.videoOutputPath = videoOutputPath;

final XFile file = await camera.stopVideoRecording(0);
assert(file.path == videoOutputPath);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be an expect statement instead of assert?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch! Changed

Copy link
Member Author

Choose a reason for hiding this comment

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

It was actually in two places because I copied this test to make the test of "call start video recording twice", changed in both places now

@gmackall gmackall added the autosubmit Merge PR when tree becomes green via auto submit App label May 8, 2023
@camsim99 camsim99 mentioned this pull request May 9, 2023
11 tasks
@camsim99
Copy link
Contributor

camsim99 commented May 9, 2023

Merging manually to unblock #3878

@camsim99 camsim99 merged commit d6bb772 into flutter:main May 9, 2023
nploi pushed a commit to nploi/packages that referenced this pull request Jul 16, 2023
…flutter#3467)

This PR implements video capture for the CameraX re-write of the android
camera plugin.

flutter/flutter#111138

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [relevant style guides] and ran the
auto-formatter. (Unlike the flutter/flutter repo, the flutter/packages
repo does use `dart format`.)
- [x] I signed the [CLA].
- [x] The title of the PR starts with the name of the package surrounded
by square brackets, e.g. `[shared_preferences]`
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated `pubspec.yaml` with an appropriate new version according
to the [pub versioning philosophy], or this PR is [exempt from version
changes].
- [x] I updated `CHANGELOG.md` to add a description of the change,
[following repository CHANGELOG style].
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/packages/blob/main/CONTRIBUTING.md
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[relevant style guides]:
https://github.com/flutter/packages/blob/main/CONTRIBUTING.md#style
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
[pub versioning philosophy]: https://dart.dev/tools/pub/versioning
[exempt from version changes]:
https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#version-and-changelog-updates
[following repository CHANGELOG style]:
https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changelog-style
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests

---------

Co-authored-by: Gray Mackall <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
autosubmit Merge PR when tree becomes green via auto submit App p: camera platform-android
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants