Skip to content

Camera control#6190

Open
FeodorFitsner wants to merge 17 commits intomainfrom
flet-camera
Open

Camera control#6190
FeodorFitsner wants to merge 17 commits intomainfrom
flet-camera

Conversation

@FeodorFitsner
Copy link
Contributor

@FeodorFitsner FeodorFitsner commented Feb 19, 2026

Summary by Sourcery

Add a new cross-platform Camera control as a first-class Flet extension and integrate it into the client, SDK, docs, CI, and examples.

New Features:

  • Introduce the flet-camera Python package and Flutter extension providing a Camera control for preview, photo capture, video recording, and image streaming.
  • Add a Camera example app demonstrating camera selection, preview, photo capture, video recording, and image streaming in Flet.
  • Document the Camera control and its related types in the Flet docs site and package README.

Bug Fixes:

  • Prevent dropdown menus from keeping a stale intrinsic width when their option set changes by forcing the menu to rebuild with a key derived from options.
  • Avoid blocking the websocket receive loop during session reconnection by running the session connect logic asynchronously and surfacing errors back to the session.

Enhancements:

  • Register the new camera and related extensions in the Flutter client startup sequence and reorder imports for consistency.
  • Clarify internal checklist docs for adding new Flet extensions and add iOS camera usage description to the client Info.plist.

Build:

  • Add flet-camera to the Python workspace, dependency graphs, and build metadata so it is built and published with other extensions.
  • Add a Flutter package for the flet_camera extension wired into the monorepo with its own pubspec and analysis options.

CI:

  • Include flet-camera in CI extension build and publish jobs so it is validated and released with the rest of the extension ecosystem.

Documentation:

  • Add Camera control and type reference pages plus navigation entries to the Flet documentation, including usage, platform support, and permissions guidance.
  • Add flet-camera to the list of built-in extensions and provide a package-level README with links to docs and platform support matrix.

Introduces the flet-camera package with Python and Flutter bindings, including camera control, types, and documentation. Integrates flet_camera into the client app, updates dependencies, and provides a usage example for camera control in Flet Python apps.
Added detailed docstrings to enums, dataclasses, and methods in camera.py and types.py for improved API documentation and clarity. Implemented CameraDescription.to_dict() for proper serialization when passing camera descriptions. Updated method argument handling to use serialized camera descriptions and clarified return values and arguments in method docstrings.
Refactored Python camera example to use a dataclass for state management, improved event handling, and added support for image streaming and preview controls. Updated camera API to remove enum-to-value conversions and dictionary serialization, passing objects directly. Adjusted Flutter implementation to match new API, including support for image streaming and controller initialization changes.
Added a ValueKey to DropdownMenu based on the options set to ensure the menu recalculates its intrinsic width when options change, preventing incorrect sizing when new, longer options are added.
Session reconnect now uses an async task to avoid blocking the websocket receive loop with user handlers. Errors during reconnect are logged and reported to the session if possible.
Replaced print statements with logging for better output control, fixed color constant typo, removed redundant camera initialization, and set up camera retrieval on page connect. These changes streamline the example and improve maintainability.
Register flet_camera and flet_code_editor (imports and extension entries) in client main, reorder some extension registrations and tidy a desktop-mode exception formatting. Update camera package usage and utils: change resolution preset parsing to use a nullable default and non-null return, replace parseOffsetFromJson calls with parseOffset, simplify parseCameraDescription to use parseEnum with defaults, remove the old parseOffsetFromJson/parseEnum implementations, and add flet import in utils. Also add the flet_camera package (path) to pubspec.lock and apply dependency lock updates.
Register the new flet-camera extension across the repository: update sdk/python/packages/flet/pyproject.toml (extensions list), add flet-camera to the example app sdk/python/examples/apps/flet_build_test/pyproject.toml (dependencies, editable package and local path mapping), and include flet-camera in the GitHub Actions workflow (.github/workflows/ci.yml) in both the build_flet_extensions PACKAGES list and the py_publish publish loop. Also clarify the SKILL.md guidance to remind to add the extension in both CI locations.
Replace debug print with logic to render incoming camera frames by setting last_image.src and calling last_image.update(). Wrap rendering in a try/except and log exceptions with logging.exception to avoid crashing the stream handler and improve robustness.
Extend the camera example with video recording capabilities: import datetime, change dropdown label to "Select camera", and add a recorded_video_path Text control. Implement detect_video_extension to infer webm/mp4/mov from bytes and add start/pause/resume/stop recording handlers that save the recorded bytes using FilePicker with a timestamped filename. Update camera state handling to correctly reflect paused vs recording states and add UI buttons for recording controls alongside the existing photo/streaming controls.
Enhance the camera example with full recording/streaming UI and state handling: add flags to State (is_streaming, is_recording, preview/recording paused, streaming support), replace text buttons with icon buttons (take photo, record, pause recording, stream, preview), and wire up toggle helpers. Introduce sync_action_buttons to keep button states in sync, update handlers to set state and call page.update(), check for image streaming support, and handle CameraState events to reflect runtime changes. Also remove unused logging setup and tweak the dropdown label.
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Feb 19, 2026

Deploying flet-examples with  Cloudflare Pages  Cloudflare Pages

Latest commit: ce08d50
Status: ✅  Deploy successful!
Preview URL: https://63ad2153.flet-examples.pages.dev
Branch Preview URL: https://flet-camera.flet-examples.pages.dev

View logs

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

We've reviewed this pull request using the Sourcery rules engine

Document required platform permissions for the Camera control: add iOS Info.plist keys (NSCameraUsageDescription, NSMicrophoneUsageDescription) and Android permission flags (android.permission.CAMERA, android.permission.RECORD_AUDIO). Clarifies that microphone permissions are only needed for audio-enabled video recording and links to the platform-specific permissions docs.
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Feb 19, 2026

Deploying flet-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: ce08d50
Status: ✅  Deploy successful!
Preview URL: https://63b9db5d.flet-docs.pages.dev
Branch Preview URL: https://flet-camera.flet-docs.pages.dev

View logs

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new flet-camera extension/control to the Flet Python SDK (with Flutter-side implementation), wiring it into docs, examples, CI, and the Flutter client app so camera preview/capture/streaming is available on supported platforms.

Changes:

  • Introduces the new flet-camera Python package + Flutter extension implementation, and registers it across workspace/CI/client dependencies.
  • Adds API docs pages and a full Python example for the Camera control.
  • Includes a couple of unrelated runtime/UI fixes: async reconnect handling in flet-web and a DropdownMenu rebuild key to recalc intrinsic width.

Reviewed changes

Copilot reviewed 31 out of 32 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
sdk/python/pyproject.toml Adds flet-camera to workspace dependencies + isort config.
sdk/python/packages/flet/pyproject.toml Registers flet-camera as a built-in extension.
sdk/python/packages/flet/mkdocs.yml Adds nav entries for Camera docs (but also duplicates Audio/AudioRecorder).
sdk/python/packages/flet/docs/extend/built-in-extensions.md Lists flet-camera among built-in extensions.
sdk/python/packages/flet/docs/camera/index.md New Camera control documentation page.
sdk/python/packages/flet/docs/camera/types/camera_description.md New type doc stub.
sdk/python/packages/flet/docs/camera/types/camera_image.md New type doc stub.
sdk/python/packages/flet/docs/camera/types/camera_state.md New type doc stub.
sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app_manager.py Changes reconnect behavior to run session.connect() in a background task.
sdk/python/packages/flet-camera/src/flutter/flet_camera/pubspec.yaml New Flutter extension package definition.
sdk/python/packages/flet-camera/src/flutter/flet_camera/lib/src/utils/camera.dart New camera parsing + image encoding utilities (pixel conversion in Dart).
sdk/python/packages/flet-camera/src/flutter/flet_camera/lib/src/camera.dart New Camera control widget + invoke-method handling.
sdk/python/packages/flet-camera/src/flutter/flet_camera/lib/src/extension.dart Registers the Camera widget with the Flet extension.
sdk/python/packages/flet-camera/src/flet_camera/types.py New Python dataclasses/enums for camera types/events.
sdk/python/packages/flet-camera/src/flet_camera/camera.py New Python Camera control wrapper with async API methods.
sdk/python/examples/controls/camera/example_1.py New end-to-end example showcasing preview, photos, video, streaming.
sdk/python/examples/apps/flet_build_test/pyproject.toml Adds flet-camera to build-test app dependencies.
packages/flet/lib/src/controls/dropdown.dart Forces DropdownMenu rebuild when options change to recalc intrinsic width.
client/pubspec.yaml Adds the Flutter flet_camera extension dependency to the client.
client/pubspec.lock Locks new transitive deps for camera/image, etc.
client/lib/main.dart Imports/registers the camera extension in the client extensions list.
client/ios/Runner/Info.plist Adds NSCameraUsageDescription for iOS.
.github/workflows/ci.yml Adds flet-camera to extension build/publish workflows.
.codex/skills/implement-flet-extension/SKILL.md Updates extension implementation checklist to reflect CI update locations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +128 to +148

# Run connect asynchronously so websocket receive loop isn't blocked by
# user handlers (e.g., on_connect invoking _invoke_method).

async def _connect():
try:
await session.connect(conn)
except Exception as e:
logger.error(
f"Unhandled error reconnecting session {session_id}: {e}",
exc_info=True,
)
try:
session.error(str(e))
except Exception:
logger.error(
"Failed to report reconnect error to session",
exc_info=True,
)

asyncio.create_task(_connect())
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

reconnect_session() now schedules session.connect(conn) in a background task and returns immediately. This introduces a race where subsequent logic (e.g. in flet_app.py right after awaiting reconnect_session) can run while the session is still considered disconnected (expires_at not cleared), causing outbound messages/updates to be dropped rather than buffered/sent. Consider keeping the connection attachment step synchronous/awaited (at least until Session.__conn is set and expires_at cleared), and only run the user connect event handlers asynchronously if needed (e.g., split Session.connect() into an attach+flush step and a separately-scheduled event dispatch).

Suggested change
# Run connect asynchronously so websocket receive loop isn't blocked by
# user handlers (e.g., on_connect invoking _invoke_method).
async def _connect():
try:
await session.connect(conn)
except Exception as e:
logger.error(
f"Unhandled error reconnecting session {session_id}: {e}",
exc_info=True,
)
try:
session.error(str(e))
except Exception:
logger.error(
"Failed to report reconnect error to session",
exc_info=True,
)
asyncio.create_task(_connect())
try:
# Await connect so that session state (e.g., connection attachment,
# expiration flags) is updated before returning to the caller.
await session.connect(conn)
except Exception as e:
logger.error(
f"Unhandled error reconnecting session {session_id}: {e}",
exc_info=True,
)
try:
session.error(str(e))
except Exception:
logger.error(
"Failed to report reconnect error to session",
exc_info=True,
)

Copilot uses AI. Check for mistakes.
Comment on lines +270 to +271
- Audio: audio/index.md
- AudioRecorder: audio_recorder/index.md
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

This nav block adds Audio and AudioRecorder under API Reference -> Controls, but those entries already exist under API Reference -> Services later in the file. Unless the intent is to move them (and remove the existing entries), this duplicates pages in the nav and can lead to confusing/duplicated sidebar structure. Consider removing the new Audio/AudioRecorder entries here (or deleting the existing ones under Services if you’re intentionally relocating them).

Suggested change
- Audio: audio/index.md
- AudioRecorder: audio_recorder/index.md

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please check this. Seems valid to me. Audio and AudioRec are already in services.
Also, you added Camera types under Controls section. They should be moved to Types section.

Image


/// admonition | Permissions
type: tip
Request camera (and microphone if recording video with audio) permissions on mobile and desktop platforms before initializing the control. You can use [`flet-permission-handler`](https://pypi.org/project/flet-permission-handler/) to prompt the user.
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The permissions note mentions requesting camera/microphone permissions on “mobile and desktop platforms”, but the Platform Support table above indicates Windows/macOS/Linux are not supported. Consider updating this text to avoid implying desktop support (or update the support table if desktop support is intended).

Suggested change
Request camera (and microphone if recording video with audio) permissions on mobile and desktop platforms before initializing the control. You can use [`flet-permission-handler`](https://pypi.org/project/flet-permission-handler/) to prompt the user.
Request camera (and microphone if recording video with audio) permissions on mobile platforms before initializing the control. You can use [`flet-permission-handler`](https://pypi.org/project/flet-permission-handler/) to prompt the user.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems legit?
Maybe we could even completely remove the platform mentions...
Also, for the flet-permission-handler link, maybe instead point to its docs page directly?

FeodorFitsner and others added 4 commits February 19, 2026 15:37
Pass image_format_group=fc.ImageFormatGroup.JPEG when starting the camera preview so frames are delivered as JPEG. This ensures consistent encoding for image streaming and downstream processing.
Move camera image encoding off the main thread by serializing CameraImage into a payload and running encoding in an Isolate. Import dart:isolate and update _processStreamImage to use Isolate.run for non-JPEG formats. Add cameraImageToPayload and encodeCameraImagePayload helpers and refactor BGRA/NV21/YUV420 encoders to accept payload maps (with stronger validation and bounds checks). Keep JPEG fast-path returning plane bytes directly. This reduces UI jank and ensures safe cross-isolate data handling.
Set gapless_playback=True on the media widget in sdk/python/examples/controls/camera/example_1.py to avoid visible reload/flicker when the source is updated (e.g., switching camera frames). This makes playback smoother in the camera example.
Copy link
Contributor

@ndonkoHenri ndonkoHenri left a comment

Choose a reason for hiding this comment

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

Docs: only 4 classes were documented. Still about 8 left.

Example: on web - i was able to record a video, and when I stopped the recording it saved as .webm into my downloads folder - awesome! This was on the first try though. On the next tries though, i didnt work :( Flow: launch with fvm flutter run -d chrome, grant permission, then click take picture or preview buttons. A flutter exception (see logs below) is shown immediately after the app launches (when await get_cameras() on line 358 is executed). The Python exception is shown when i click a button.

Logs

Received message: [2, {id: 1, patch: [[0, {views: [1, {0: [2]}]}], [0, 0, title, Camera control], [0, 0, on_connect, true], [0, 2, padding, 16], [0, 2, scroll, auto], [0, 2, horizontal_alignment, stretch], [0, 2, controls, [{_i:
118, _c: SafeArea, content: {_i: 117, _c: Column, _internals: {host_expanded: true}, controls: [{_i: 113, _c: Row, _internals: {host_expanded: true}, controls: [{_i: 105, _c: Dropdown, on_select: true, label: Camera}, {_i: 112,
_c: IconButton, icon: 71733, on_click: true}], wrap: true}, {_i: 114, _c: Container, _internals: {skip_properties: [width, height, margin]}, height: 320, content: {_i: 102, _c: Camera, expand: true, content: {_i: 101, _c:
Container, _internals: {skip_properties: [width, height, margin]}, content: {_i: 100, _c: Icon, icon: 66817, color: white70, size: 48}, alignment: {x: 0, y: 0}}, on_state_change: true, on_stream_image: true}, bgcolor: black,
border_radius: 3}, {_i: 103, _c: Text, value: Select a camera, size: 12}, {_i: 115, _c: Row, _internals: {host_expanded: true}, controls: [{_i: 107, _c: FilledIconButton, tooltip: Take photo, icon: 71254, on_click: true}, {_i:
108, _c: FilledTonalIconButton, tooltip: Start / stop recording, icon: 73878, selected: false, selected_icon: 72931, on_click: true}, {_i: 109, _c: OutlinedIconButton, tooltip: Pause / resume recording, disabled: true, icon:
70989, selected: false, selected_icon: 71356, on_click: true}, {_i: 110, _c: OutlinedIconButton, tooltip: Start / stop image stream, visible: false, icon: 71356, selected: false, selected_icon: 72931, on_click: true}, {_i: 111,
_c: OutlinedIconButton, tooltip: Pause / resume preview, icon: 73987, selected: true, selected_icon: 73986, on_click: true}], wrap: true}, {_i: 106, _c: Text, value: Recorded video: not saved yet, size: 12}, {_i: 116, _c: Text,
value: Last photo}, {_i: 104, _c: Image, _internals: {skip_properties: [width, height]}, height: 200, src: iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wIAAgMBAp0YVwAAAABJRU5ErkJggg==, fit: contain,
gapless_playback: true}]}}]]]}]
Control(1).applyPatch: [[0, {views: [1, {0: [2]}]}], [0, 0, title, Camera control], [0, 0, on_connect, true], [0, 2, padding, 16], [0, 2, scroll, auto], [0, 2, horizontal_alignment, stretch], [0, 2, controls, [{_i: 118, _c:
SafeArea, content: {_i: 117, _c: Column, _internals: {host_expanded: true}, controls: [{_i: 113, _c: Row, _internals: {host_expanded: true}, controls: [{_i: 105, _c: Dropdown, on_select: true, label: Camera}, {_i: 112, _c:
IconButton, icon: 71733, on_click: true}], wrap: true}, {_i: 114, _c: Container, _internals: {skip_properties: [width, height, margin]}, height: 320, content: {_i: 102, _c: Camera, expand: true, content: {_i: 101, _c: Container,
_internals: {skip_properties: [width, height, margin]}, content: {_i: 100, _c: Icon, icon: 66817, color: white70, size: 48}, alignment: {x: 0, y: 0}}, on_state_change: true, on_stream_image: true}, bgcolor: black, border_radius:
3}, {_i: 103, _c: Text, value: Select a camera, size: 12}, {_i: 115, _c: Row, _internals: {host_expanded: true}, controls: [{_i: 107, _c: FilledIconButton, tooltip: Take photo, icon: 71254, on_click: true}, {_i: 108, _c:
FilledTonalIconButton, tooltip: Start / stop recording, icon: 73878, selected: false, selected_icon: 72931, on_click: true}, {_i: 109, _c: OutlinedIconButton, tooltip: Pause / resume recording, disabled: true, icon: 70989,
selected: false, selected_icon: 71356, on_click: true}, {_i: 110, _c: OutlinedIconButton, tooltip: Start / stop image stream, visible: false, icon: 71356, selected: false, selected_icon: 72931, on_click: true}, {_i: 111, _c:
OutlinedIconButton, tooltip: Pause / resume preview, icon: 73987, selected: true, selected_icon: 73986, on_click: true}], wrap: true}, {_i: 106, _c: Text, value: Recorded video: not saved yet, size: 12}, {_i: 116, _c: Text,
value: Last photo}, {_i: 104, _c: Image, _internals: {skip_properties: [width, height]}, height: 200, src: iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wIAAgMBAp0YVwAAAABJRU5ErkJggg==, fit: contain,
gapless_playback: true}]}}]]], shouldNotify = true
Notify Page(1)
Page updated: TargetPlatform.macOS TargetPlatform.macOS
Notify Page(1)
Page updated: TargetPlatform.macOS TargetPlatform.macOS
Notify View(94)
Notify View(94)
Notify View(94)
Notify View(94)
Received message: [5, {control_id: 102, call_id: ayGpSZOPQh, name: get_available_cameras}]
Camera(102).get_available_cameras(null)
Page.didUpdateWidget: 1
Page.build: 1
Page navigator build: 1
View.didUpdateWidget: 94
View.build: 94
ScrollableControl build: 94
SafeArea build: 118
Column build: 117
ScrollableControl build: 117
Row build: 113
ScrollableControl build: 113
DropdownMenu build: 105
IconButton build: 112
Container build: 114
Camera build: 102
Text build: 103
Row build: 115
ScrollableControl build: 115
IconButton build: 107
IconButton build: 108
IconButton build: 109
IconButton build: 111
Text build: 106
Text build: 116
Image build: 104
Camera.get_available_cameras(null)
══╡ EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE ╞════════════════════════════════════════════════════
The following DomException object was thrown resolving an image frame:
  EncodingError: Failed to decode frame at index 0

When the exception was thrown, this was the stack
════════════════════════════════════════════════════════════════════════════════════════════════════
_send: MessageAction.controlEvent {target: 1, name: app_lifecycle_state_change, data: {state: inactive}}
_send: MessageAction.controlEvent {target: 1, name: app_lifecycle_state_change, data: {state: resume}}
_send: MessageAction.invokeControlMethod {control_id: 102, call_id: ayGpSZOPQh, result: [{name: MacBook Pro Camera (0000:0001), lens_direction: external, sensor_orientation: 0, lens_type: unknown}, {name: OBS Virtual Camera,
lens_direction: external, sensor_orientation: 0, lens_type: unknown}], error: null}
Received message: [2, {id: 1, patch: [[0, {views: [1, {0: [2, {controls: [3, {0: [4, {content: [5, {controls: [6, {0: [7, {controls: [8, {0: [9]}]}]}]}]}]}]}]}]}], [0, 9, options, [{_i: 119, _c: DropdownOption, key: MacBook Pro
Camera (0000:0001), text: MacBook Pro Camera (0000:0001)}, {_i: 120, _c: DropdownOption, key: OBS Virtual Camera, text: OBS Virtual Camera}]]]}]
Control(1).applyPatch: [[0, {views: [1, {0: [2, {controls: [3, {0: [4, {content: [5, {controls: [6, {0: [7, {controls: [8, {0: [9]}]}]}]}]}]}]}]}]}], [0, 9, options, [{_i: 119, _c: DropdownOption, key: MacBook Pro Camera
(0000:0001), text: MacBook Pro Camera (0000:0001)}, {_i: 120, _c: DropdownOption, key: OBS Virtual Camera, text: OBS Virtual Camera}]]], shouldNotify = true
Notify Dropdown(105)
DropdownMenu build: 105
OutlinedIconButton(111).on_click(null)
_send: MessageAction.controlEvent {target: 111, name: click, data: null}
Received message: [5, {control_id: 102, call_id: QTb9SabhZz, name: pause_preview}]
Camera(102).pause_preview(null)
Camera.pause_preview(null)
_send: MessageAction.invokeControlMethod {control_id: 102, call_id: QTb9SabhZz, result: null, error: Exception: Camera is not initialized. Call initialize() first.}
Received message: [6, {message: Exception: Camera is not initialized. Call initialize() first.
Traceback (most recent call last):
  File "/Users/ndonkohenri/PycharmProjects/flet-dev/flet/sdk/python/packages/flet/src/flet/messaging/session.py", line 319, in dispatch_event
    await control._trigger_event(event_name, event_data)
  File "/Users/ndonkohenri/PycharmProjects/flet-dev/flet/sdk/python/packages/flet/src/flet/controls/base_control.py", line 414, in _trigger_event
    await event_handler()
  File "/Users/ndonkohenri/PycharmProjects/flet-dev/flet/sdk/python/examples/controls/camera/example_1.py", line 312, in toggle_preview
    await pause_preview()
  File "/Users/ndonkohenri/PycharmProjects/flet-dev/flet/sdk/python/examples/controls/camera/example_1.py", line 279, in pause_preview
    await preview.pause_preview()
  File "/Users/ndonkohenri/PycharmProjects/flet-dev/flet/sdk/python/packages/flet-camera/src/flet_camera/camera.py", line 134, in pause_preview
    await self._invoke_method("pause_preview")
  File "/Users/ndonkohenri/PycharmProjects/flet-dev/flet/sdk/python/packages/flet/src/flet/controls/base_control.py", line 360, in _invoke_method
    return await self.page.session.invoke_method(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ndonkohenri/PycharmProjects/flet-dev/flet/sdk/python/packages/flet/src/flet/messaging/session.py", line 374, in invoke_method
    raise RuntimeError(err)
RuntimeError: Exception: Camera is not initialized. Call initialize() first.
}]

[![pypi](https://img.shields.io/pypi/v/flet-camera.svg)](https://pypi.python.org/pypi/flet-camera)
[![downloads](https://static.pepy.tech/badge/flet-camera/month)](https://pepy.tech/project/flet-camera)
[![python](https://img.shields.io/badge/python-%3E%3D3.10-%2334D058)](https://pypi.org/project/flet-camera)
[![docstring coverage](https://docs.flet.dev/assets/badges/docs-coverage/flet-camera.svg)](https://github.com/flet-dev/flet/tree/main/sdk/python/packages/flet/docs/assets/badges/docs-coverage)
Copy link
Contributor

Choose a reason for hiding this comment

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

Lets point the link to the image? The docs-coverage folder isnt quite useful.

Suggested change
[![docstring coverage](https://docs.flet.dev/assets/badges/docs-coverage/flet-camera.svg)](https://github.com/flet-dev/flet/tree/main/sdk/python/packages/flet/docs/assets/badges/docs-coverage)
[![docstring coverage](https://docs.flet.dev/assets/badges/docs-coverage/flet-camera.svg)](https://docs.flet.dev/assets/badges/docs-coverage/flet-camera.svg)

[![docstring coverage](https://docs.flet.dev/assets/badges/docs-coverage/flet-camera.svg)](https://github.com/flet-dev/flet/tree/main/sdk/python/packages/flet/docs/assets/badges/docs-coverage)
[![license](https://img.shields.io/badge/License-Apache_2.0-green.svg)](https://github.com/flet-dev/flet/blob/main/sdk/python/packages/flet-camera/LICENSE)

A cross-platform camera control for [Flet](https://flet.dev) apps.
Copy link
Contributor

Choose a reason for hiding this comment

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

Doenst seem fully "cross-platform" though 😅

Comment on lines +7 to +8
// ignore: implementation_imports
import 'package:flet/src/controls/control_widget.dart';
Copy link
Contributor

Choose a reason for hiding this comment

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

Export this file lib/flet.dart? Its kind of a util.

export 'src/controls/control_widget.dart';

Comment on lines +51 to +53
DeviceOrientation? parseDeviceOrientation(dynamic value) {
return parseEnum(DeviceOrientation.values, value);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Exists in utils/device_info.dart, but needs to be exposed/exported in lib/flet.dart.

Comment on lines +35 to +49
ImageFormatGroup? parseImageFormatGroup(dynamic value) {
return parseEnum(ImageFormatGroup.values, value);
}

FlashMode? parseFlashMode(dynamic value) {
return parseEnum(FlashMode.values, value);
}

ExposureMode? parseExposureMode(dynamic value) {
return parseEnum(ExposureMode.values, value);
}

FocusMode? parseFocusMode(dynamic value) {
return parseEnum(FocusMode.values, value);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Add defaultValue param.

Comment on lines +1 to +8
---
class_name: flet_camera.CameraState
separate_signature: false
---

# CameraState

{{ class_all_options(class_name) }}
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
---
class_name: flet_camera.CameraState
separate_signature: false
---
# CameraState
{{ class_all_options(class_name) }}
{{ class_all_options("flet_camera.CameraState") }}

Comment on lines +1 to +8
---
class_name: flet_camera.CameraDescription
separate_signature: false
---

# CameraDescription

{{ class_all_options(class_name) }}
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
---
class_name: flet_camera.CameraDescription
separate_signature: false
---
# CameraDescription
{{ class_all_options(class_name) }}
{{ class_all_options("flet_camera.CameraDescription") }}

recorded_video_path.value = "Recorded video save canceled"
page.update()

async def on_state_change(e: ft.Event[fc.CameraState]):
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
async def on_state_change(e: ft.Event[fc.CameraState]):
async def on_state_change(e: fc.CameraState):

Or CameraStateEvent as suggested in another comment.

Comment on lines +73 to +82
on_click=lambda _: None,
tooltip="Start / stop image stream",
visible=False,
)
preview_btn = ft.OutlinedIconButton(
icon=ft.Icons.VISIBILITY_OFF,
selected_icon=ft.Icons.VISIBILITY,
selected=True,
on_click=lambda _: None,
tooltip="Pause / resume preview",
Copy link
Contributor

@ndonkoHenri ndonkoHenri Feb 20, 2026

Choose a reason for hiding this comment

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

lambda _: None is honestly not a good style.
Will suggest you move these declarations below the events callbacks so we can set them directly.

Comment on lines +107 to +119
def detect_video_extension(data: bytes) -> str:
# Matroska/WebM EBML header.
if data.startswith(b"\x1a\x45\xdf\xa3"):
return "webm"

# ISO BMFF (mp4/mov) starts with a box size + "ftyp".
if len(data) >= 12 and data[4:8] == b"ftyp":
brand = data[8:12]
if brand == b"qt ":
return "mov"
return "mp4"

return "bin"
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems pretty handy. Should we add it to package too? perhaps as util or method of a class?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments