-
Notifications
You must be signed in to change notification settings - Fork 3.5k
[camera_web] Re: Support for camera stream on web #7950
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
base: main
Are you sure you want to change the base?
Conversation
…d before expecting mock response
…d before expecting mock response
…d before expecting mock response
…d before expecting mock response
…d before expecting mock response
…d before expecting mock response
…m, and used window.animationFrames
…le height and width in takeFrame
…seOffScreenCanvas
…m takeFrame function of camera service, using the function directly
…or instead of intialize()
…t OffscreenCanvas in camera service
…t OffscreenCanvas in camera test
5721618 to
d7b7d5c
Compare
|
Hi @ditman The PR is ready for another review, Thanks :) |
|
From triage: Ping @ditman on this re-review. |
|
Update from triage: we're working through trying to find a web reviewer with availability to look at this. Apologies for the delay. |
|
@yjbanov can you suggest a reviewer for this change? It has been blocked on a web review. |
|
/gemini review |
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.
Code Review
This PR introduces support for image streams on the web for the camera plugin. The changes are well-structured, with new functionality added to Camera and CameraService classes, and corresponding tests.
My review has identified a few areas for improvement:
- A critical issue in
CameraServicewhere a null-check operator could lead to a runtime error. - Some maintainability issues in
Camerarelated to a C-style variable declaration and a magic number. - Opportunities to reduce code duplication in the new integration tests.
Overall, this is a great contribution. Addressing these points will improve the robustness and maintainability of the code.
| required int fps, | ||
| }) { | ||
| int? animationFrameId; | ||
| num then = 0, fpsInterval = 1000 / fps; |
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.
and let's give then a more descriptive name like lastFrameTimestamp.
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 is addressed.
| final num elapsed = timestamp - then; | ||
|
|
||
| // if we're close to the next frame (by ~8ms), do it. | ||
| if (fpsInterval - elapsed <= 8) { |
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.
This is addressed.
| testWidgets( | ||
| 'returns true if broswer has OffscreenCanvas ' | ||
| 'otherwise false', | ||
| (WidgetTester widgetTester) async { | ||
| when( | ||
| () => jsUtil.hasProperty( | ||
| window, | ||
| 'OffscreenCanvas'.toJS, | ||
| ), | ||
| ).thenReturn(true); | ||
| final bool hasOffScreenCanvas = | ||
| cameraService.hasPropertyOffScreenCanvas(); | ||
| expect( | ||
| hasOffScreenCanvas, | ||
| true, | ||
| ); | ||
| when( | ||
| () => jsUtil.hasProperty( | ||
| window, | ||
| 'OffscreenCanvas'.toJS, | ||
| ), | ||
| ).thenReturn(false); | ||
| final bool hasNotOffScreenCanvas = | ||
| cameraService.hasPropertyOffScreenCanvas(); | ||
| expect( | ||
| hasNotOffScreenCanvas, | ||
| false, | ||
| ); | ||
| }, |
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 test and the subsequent test on lines 944-1003 are nearly identical, with the only difference being the mocked return value of hasPropertyOffScreenCanvas. This duplication can be reduced by creating a shared test implementation or using a for loop to run the test for both true and false cases. This will make the tests more maintainable.
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 is addressed now.
| testWidgets( | ||
| 'CameraImageData bytes is a multiple of 4 ' | ||
| 'when browser supports OffscreenCanvas', | ||
| (WidgetTester tester) async { | ||
| final VideoElement videoElement = getVideoElementWithBlankStream( | ||
| const Size(10, 10), | ||
| ); | ||
|
|
||
| final Camera camera = Camera( | ||
| textureId: textureId, | ||
| cameraService: cameraService, | ||
| )..videoElement = videoElement; | ||
|
|
||
| when(() => cameraService.hasPropertyOffScreenCanvas()).thenReturn( | ||
| true, | ||
| ); | ||
|
|
||
| when( | ||
| () => cameraService.takeFrame(videoElement), | ||
| ).thenAnswer( | ||
| (_) => CameraImageData( | ||
| format: const CameraImageFormat( | ||
| ImageFormatGroup.unknown, | ||
| raw: 0, | ||
| ), | ||
| planes: <CameraImagePlane>[ | ||
| CameraImagePlane( | ||
| bytes: Uint8List(32), | ||
| bytesPerRow: videoElement.width * 4, | ||
| ), | ||
| ], | ||
| height: 10, | ||
| width: 10, | ||
| ), | ||
| ); | ||
|
|
||
| final CameraImageData cameraImageData = | ||
| await camera.cameraFrameStream().first; | ||
| expect( | ||
| cameraImageData, | ||
| equals( | ||
| isA<CameraImageData>().having( | ||
| (CameraImageData e) => e.planes.first.bytes.length % 4, | ||
| 'bytes', | ||
| equals(0), | ||
| ), | ||
| ), | ||
| ); | ||
| }, |
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 test and the subsequent test on lines 1763-1812 are almost identical, differing only in the mocked value for hasPropertyOffScreenCanvas. To improve maintainability and reduce code duplication, consider refactoring them into a single test function that is parameterized with the boolean for OffscreenCanvas support, or use a loop.
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 has been addressed.
| // TODO(replace): introduced fps in | ||
| /// [CameraImageStreamOptions] |
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 don't think this TODO needs to be addressed in this PR, because PR checks would fail if it would contain changes in more than one package.
|
From triage: @mdebbar What's the status of the web review here? |
mdebbar
left a comment
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 looks really close to being ready to land. I just some minor polishing comments.
| ## NEXT | ||
| ## 0.3.6 | ||
|
|
||
| * Supporting camera image stream on web. |
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.
To keep this consistent with the rest of the file:
| * Supporting camera image stream on web. | |
| * Adds support for camera image stream on web. |
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 is addressed.
| when( | ||
| () => jsUtil.hasProperty( | ||
| window, | ||
| 'OffscreenCanvas'.toJS, | ||
| ), | ||
| ).thenReturn(true); | ||
| final bool hasOffScreenCanvas = | ||
| cameraService.hasPropertyOffScreenCanvas(); | ||
| expect( | ||
| hasOffScreenCanvas, | ||
| true, | ||
| ); | ||
| when( | ||
| () => jsUtil.hasProperty( | ||
| window, | ||
| 'OffscreenCanvas'.toJS, | ||
| ), | ||
| ).thenReturn(false); | ||
| final bool hasNotOffScreenCanvas = | ||
| cameraService.hasPropertyOffScreenCanvas(); | ||
| expect( | ||
| hasNotOffScreenCanvas, | ||
| false, | ||
| ); |
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 use a loop to simplify:
| when( | |
| () => jsUtil.hasProperty( | |
| window, | |
| 'OffscreenCanvas'.toJS, | |
| ), | |
| ).thenReturn(true); | |
| final bool hasOffScreenCanvas = | |
| cameraService.hasPropertyOffScreenCanvas(); | |
| expect( | |
| hasOffScreenCanvas, | |
| true, | |
| ); | |
| when( | |
| () => jsUtil.hasProperty( | |
| window, | |
| 'OffscreenCanvas'.toJS, | |
| ), | |
| ).thenReturn(false); | |
| final bool hasNotOffScreenCanvas = | |
| cameraService.hasPropertyOffScreenCanvas(); | |
| expect( | |
| hasNotOffScreenCanvas, | |
| false, | |
| ); | |
| for (final supportsOffscreenCanvas in [true, false]) { | |
| when( | |
| () => jsUtil.hasProperty( | |
| window, | |
| 'OffscreenCanvas'.toJS, | |
| ), | |
| ).thenReturn(value); | |
| final bool hasOffScreenCanvas = | |
| cameraService.hasPropertyOffScreenCanvas(); | |
| expect( | |
| cameraService.hasPropertyOffScreenCanvas(), | |
| supportsOffscreenCanvas ? isTrue : isFalse, | |
| ); | |
| } |
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.
Just so you know – this has been addressed now.
| testWidgets( | ||
| 'returns Camera Image of Size ' | ||
| 'when videoElement is of Size ' | ||
| 'when browser supports OffscreenCanvas', | ||
| (WidgetTester widgetTester) 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.
This test and the one below are basically the same, the only difference is the boolean value of the hasPropertyOffScreenCanvas mock.
Combine both into a single test:
testWidgets(
'returns Camera Image of Size '
'when videoElement is of Size '
'regardless of OffscreenCanvas support',
(WidgetTester widgetTester) async {
for (final supportsOffscreenCanvas in [true, false]) {
...
}
}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 has been resolved now.
| testWidgets( | ||
| 'CameraImageData bytes is a multiple of 4 ' | ||
| 'when browser supports OffscreenCanvas', | ||
| (WidgetTester tester) 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.
Can be combined with the test below, similar to my previous comment.
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've taken care of this.
|
|
||
| /// Used to check if allowed to paint canvas off screen | ||
| @visibleForTesting | ||
| bool canUseOffscreenCanvas = false; |
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 like this never changes, so let's make it final:
| bool canUseOffscreenCanvas = false; | |
| final bool canUseOffscreenCanvas; |
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 is addressed now.
| // TODO(replace): introduced fps in | ||
| /// [CameraImageStreamOptions] |
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.
Please follow the style guide for TODOs: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md#avoid-checking-in-comments-that-ask-questions
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 updated the TODO comment. But haven't created an issue for that. Should I create one?
Update: I've created an issue and included that in the comment.
| required int fps, | ||
| }) { | ||
| int? animationFrameId; | ||
| num then = 0, fpsInterval = 1000 / fps; |
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.
and let's give then a more descriptive name like lastFrameTimestamp.
|
From triage: @mdebbar This looks to be ready for re-review. |
This PR aims to provide support for strartImageStream and stopImageStream on Web.
#92460
Based on #6944 from the archived plugins repository
Pre-launch Checklist
dart format.)[shared_preferences]pubspec.yamlwith an appropriate new version according to the [pub versioning philosophy], or this PR is [exempt from version changes].CHANGELOG.mdto add a description of the change, [following repository CHANGELOG style].///).POV: my first PR on a public repo
Contains required commits from the PR #6443, resolves unnecessary 202 commits