-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
recordingFlutterApi = new RecordingFlutterApiImpl(binaryMessenger, instanceManager); | ||
} | ||
|
||
public void setContext(Context context) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: add @NonNull
There was a problem hiding this comment.
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 = |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
.
...era_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java
Show resolved
Hide resolved
cameraController == null ? null : | ||
(cameraController.value.isRecordingPaused | ||
? onResumeButtonPressed : onPauseButtonPressed), | ||
onPressed: cameraController == null |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this 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; |
There was a problem hiding this comment.
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?
...ra/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java
Show resolved
Hide resolved
} | ||
|
||
void create(@NonNull PendingRecording pendingRecording, @Nullable Reply<Void> reply) { | ||
create(instanceManager.addHostCreatedInstance(pendingRecording), reply); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added to flutter/flutter#125926
public Long start(@NonNull Long identifier) { | ||
PendingRecording pendingRecording = getPendingRecordingFromInstanceId(identifier); | ||
Recording recording = | ||
pendingRecording.start(this.getExecutor(), x -> handleVideoRecordEvent(x)); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: add javadoc
return instanceManager.getInstanceWithWeakReference(videoCaptureId)! | ||
as VideoCapture; |
There was a problem hiding this comment.
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:
return instanceManager.getInstanceWithWeakReference(videoCaptureId)! | |
as VideoCapture; | |
return instanceManager.getInstanceWithWeakReference<VideoCapture>(videoCaptureId)!; |
packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
Show resolved
Hide resolved
when(mockApi.start(pendingRecordingId)).thenReturn(mockRecordingId); | ||
expect(await pendingRecording.start(), mockRecording); | ||
verify(mockApi.start(pendingRecordingId)); | ||
}); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this 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()); |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
...era/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java
Show resolved
Hide resolved
There was a problem hiding this 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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Creates a VideoCapture associated with the given [Recorder]. | |
/// Creates a [VideoCapture] associated with the given [Recorder]. |
packages/camera/camera_android_camerax/lib/src/system_services.dart
Outdated
Show resolved
Hide resolved
@@ -471,6 +484,83 @@ void main() { | |||
expect(previewTexture.textureId, equals(textureId)); | |||
}); | |||
|
|||
test( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a test for
- if the recording is null (we haven't started a recording). In this case we throw a CameraException
- if the videoOutputPath is null (we shouldn't generally reach this case). In this case we throw a CameraException
- 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).
There was a problem hiding this comment.
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)!); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
before continuing my pass I need the cla to be figured out. |
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this 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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)); | ||
}); |
There was a problem hiding this comment.
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?
There was a problem hiding this 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added to flutter/flutter#125926
|
||
/// Closes the specified recording instance. | ||
Future<void> closeFromInstance(Recording recording) async { | ||
close(instanceManager.getIdentifier(recording)!); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)); | ||
}); |
There was a problem hiding this comment.
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
…ng test to cover case. Also change to null videoOutputPath error case and test
To make reviewing easier (because this has been updated a lot), since your last review @reidbaker the summary of changes is:
|
@@ -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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Changed
There was a problem hiding this comment.
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
Merging manually to unblock #3878 |
…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]>
This PR implements video capture for the CameraX re-write of the android camera plugin.
flutter/flutter#111138
Pre-launch Checklist
dart format
.)[shared_preferences]
pubspec.yaml
with an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.CHANGELOG.md
to add a description of the change, following repository CHANGELOG style.///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.