Repository navigation
[camera_web] Re: Support for camera stream on web - #7950
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
mdebbar
left a comment
There was a problem hiding this comment.
This is looking good! Last round before we merge!
Co-authored-by: Mouad Debbar <[email protected]>
|
Can we re-run CI? Fixed a merged conflict few days back. |
|
I think it is ready to merge. Can anyone add |
flutter-zl
left a comment
There was a problem hiding this comment.
LGTM, thanks for working on this!
I tested it end to end in Chrome with before/after demos:
- Before (camera_web 0.3.5+7): https://flutter-demo-96-before.web.app, where startImageStream throws UnimplementedError
- After (this PR): https://flutter-demo-96-after.web.app, where RGBA frames stream at the camera's rate, and stopping and restarting the stream back to back stays at one loop
…6210) flutter/packages@63881f9...565c657 2026-11-08 [email protected] Roll Flutter from e36b370 to acade0f (1 revision) (flutter/packages#13590) 2026-11-07 [email protected] [material_ui] Fix DropdownMenu losing text cursor when re-enabled (flutter/packages#13179) 2026-11-07 [email protected] Roll Flutter from 09175cd to e36b370 (22 revisions) (flutter/packages#13586) 2026-11-07 [email protected] [material_ui] Add Material motion springs (flutter/packages#13338) 2026-11-06 [email protected] [material_ui] Paint the active RoundedRectSliderTrackShape segment on top in RTL (flutter/packages#13225) 2026-11-06 [email protected] [pigeon] Replace Experimental with Preview (flutter/packages#13579) 2026-11-06 [email protected] [material_ui] Fix minimum width for Material 3 primary tab indicator (flutter/packages#13322) 2026-11-06 [email protected] [material_ui] Dispose drag-end CurvedAnimation in modal bottom sheet (flutter/packages#13167) 2026-11-06 [email protected] [camera_web] Re: Support for camera stream on web (flutter/packages#7950) 2026-11-06 [email protected] Roll Flutter from 3623d2d to 09175cd (61 revisions) (flutter/packages#13578) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Description
Adds image streaming to
camera_web, soCameraController.startImageStreamworks on web.CameraPlugin.supportsImageStreaming()now returnstrue, andonStreamedFrameAvailablereturns a stream of RGBACameraImageDataframes.How frames are captured
There are two capture paths, chosen per camera when it is created:
MediaStreamTrackProcessoravailable (Chromium-based browsers)MediaStreamTrackProcessor→ReadableStream<VideoFrame>→VideoFrame.copyTo(format: 'RGBA')maxBufferSize: 1, so the processor keeps only the newest frame when the consumer falls behind)requestAnimationFrameloop → draw the<video>element to anOffscreenCanvas(or a regular canvas ifOffscreenCanvasis unavailable) →getImageDataBoth paths emit a single-plane RGBA image (
bytesPerPixel: 4,bytesPerRowset to the stride the browser reports).formatisImageFormatGroup.unknownwithraw: 'rgba8888', because the platform interface has no RGBA group yet (see TODO, flutter/flutter#151193).Frame ownership
Every emitted frame has its own buffer. The
copyTodestination is allocated per frame, andJSUint8Array.toDartreturns a view of it, not a second copy. This matches the native implementations: a consumer can keep aCameraImagepast the callback, for example across anawaitor when sending it to an isolate, and later frames won't change its bytes.An earlier revision reused one buffer across frames. I benchmarked both approaches before switching (Chrome 155 with the fake camera; 640×480 to 1080p at 30, 60 and 90 fps; 3 runs per case):
A buffer pool would need an explicit release API on the platform interface, so I've left it as a possible follow-up.
Lifecycle and errors
Camera.dispose()now closes the stream.VideoFrameis always closed after it is copied, including on errors. The reader lock is released when the loop ends.unableToCloneFrame,missingPlaneLayout). The stream closes without an error if the track reader closes (videoTrackReaderClosed).onStreamedFrameAvailable, aCameraWebExceptionis reported as a camera error event and rethrown as aPlatformException.CameraErrorCodes:noVideoTrack,videoTrackReaderClosed,videoTrackReaderNotInitialized,cameraFrameDimensionsZero,unableToCloneFrame,missingPlaneLayout.Refactors in
CameraServiceDOMException→CameraWebExceptionmapping now lives in_domExceptionToCameraWebException, shared bygetMediaStreamForOptionsand the new frame-stream methods.DOMExceptions are now detected withisA<DOMException>()instead ofon DOMException, so the check behaves the same under dart2wasm.hasMediaStreamTrackProcessor,hasPropertyOffScreenCanvas,takeFrame,getMediaStreamVideoTrack,getMediaStreamTrackReader,readVideoTrack,copyVideoFrameToBufferAndGetStride,getCameraImageData.Known limitations / follow-ups
MediaStreamTrackProcessorpath delivers whatever the track produces.CameraImageStreamOptionshas no fps field (TODO, [camera] Add framesPerSecond parameter to CameraImageStreamOptions flutter#176148).ImageFormatGroup([camera_android_camerax] Support RGBA_8888 image format group flutter#151193).MediaStreamTrackProcessorpath. When Chrome crops and scales the track to fit the requested resolution (e.g.ResolutionPreset.low= 320×240 on a 1080p camera), theVideoFrame'svisibleRectis the crop (1440×1080), and onlydisplayWidth/displayHeightare 320×240. Frames are copied atvisibleRectsize, so they can be larger thanpreviewSize. Scaling to the display size could be a follow-up.Fixes flutter/flutter#92460
Testing
flutter drive -d chromefor each file inexample/integration_test/):camera_web_stream_test.dart:supportsImageStreaming,onStreamedFrameAvailable, and thePlatformExceptionmapping.camera_test.dart, in thecameraFrameStreamgroup:CameraImageDatafails;unableToCloneFrame;camera_service_test.dart: each newCameraServicemethod, including its error cases.camera_error_code_test.dart: the new error codes.startImageStreamin Chrome using a fake camera (--use-file-for-fake-video-capture) at 640×480 to 1080p and 30–90 fps. That covered frame dimensions, stride and byte length, stopping and restarting the stream, background tabs, and dispose.Pre-Review Checklist
[shared_preferences]///).