Camera#
Display a live camera preview, capture photos and videos, and stream camera frames directly in your Flet apps.
Powered by the camera Flutter package.
Platform Support#
| Platform | iOS | Android | Web | Windows | macOS | Linux |
|---|---|---|---|---|---|---|
| Supported | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
Usage#
Add the flet-camera package to your project dependencies:
Permissions
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 to prompt the user.
iOS required Info.plist keys#
Add these entries when building for iOS:
[tool.flet.ios.info]
NSCameraUsageDescription = "This app uses the camera to capture photos and video."
NSMicrophoneUsageDescription = "This app uses the microphone when recording video with audio."
NSCameraUsageDescriptionis required for camera preview/capture.NSMicrophoneUsageDescriptionis required whenenable_audio=Truefor video recording.
See also: iOS permissions.
Android required permissions#
For Android, enable camera permission, and microphone permission if recording video with audio:
[tool.flet.android.permission]
"android.permission.CAMERA" = true
"android.permission.RECORD_AUDIO" = true
android.permission.CAMERAis required for camera usage.android.permission.RECORD_AUDIOis required only for video recording with audio.
See also: Android permissions.
Example#
import logging
from dataclasses import dataclass, field
from datetime import datetime
import flet as ft
import flet_camera as fc
@dataclass
class State:
cameras: list[fc.CameraDescription] = field(default_factory=list)
selected_camera: fc.CameraDescription | None = None
camera_labels: dict[str, str] = field(default_factory=dict)
is_streaming: bool = False
is_streaming_supported: bool = False
is_preview_paused: bool = False
is_recording: bool = False
is_recording_paused: bool = False
async def main(page: ft.Page):
page.title = "Camera control"
page.padding = 16
page.scroll = ft.ScrollMode.AUTO
page.horizontal_alignment = ft.CrossAxisAlignment.STRETCH
state = State()
preview = fc.Camera(
expand=True,
preview_enabled=True,
content=ft.Container(
alignment=ft.Alignment.CENTER,
content=ft.Icon(
ft.Icons.CENTER_FOCUS_STRONG,
color=ft.Colors.WHITE_70,
size=48,
),
),
)
status = ft.Text(value="Select a camera", size=12)
last_image = ft.Image(
src="iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wIAAgMBAp0YVwAAAABJRU5ErkJggg==",
height=200,
fit=ft.BoxFit.CONTAIN,
gapless_playback=True,
)
selector = ft.Dropdown(label="Camera", options=[])
recorded_video_path = ft.Text(value="Recorded video: not saved yet", size=12)
take_photo_btn = ft.FilledIconButton(
icon=ft.Icons.PHOTO_CAMERA, on_click=lambda _: None, tooltip="Take photo"
)
record_btn = ft.FilledTonalIconButton(
icon=ft.Icons.VIDEOCAM,
selected_icon=ft.Icons.STOP,
selected=False,
on_click=lambda _: None,
tooltip="Start / stop recording",
)
pause_recording_btn = ft.OutlinedIconButton(
icon=ft.Icons.PAUSE,
selected_icon=ft.Icons.PLAY_ARROW,
selected=False,
on_click=lambda _: None,
tooltip="Pause / resume recording",
disabled=True,
)
stream_btn = ft.OutlinedIconButton(
icon=ft.Icons.PLAY_ARROW,
selected_icon=ft.Icons.STOP,
selected=False,
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",
)
def has_human_readable_name(camera: fc.CameraDescription) -> bool:
name = camera.name.strip()
if not name:
return False
if name.startswith("com.apple.avfoundation."):
return False
return not (":" in name and "." in name)
def camera_label(camera: fc.CameraDescription) -> str:
if has_human_readable_name(camera):
return camera.name
direction = camera.lens_direction.value.capitalize()
lens_map = {
"wide": "Wide",
"telephoto": "Telephoto",
"ultraWide": "Ultra Wide",
"unknown": "Unknown",
}
lens_type = lens_map.get(camera.lens_type.value, camera.lens_type.value)
return f"{direction} ({lens_type})"
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"
async def get_cameras():
state.cameras = await preview.get_available_cameras()
state.camera_labels.clear()
seen_labels: dict[str, int] = {}
for camera in state.cameras:
label = camera_label(camera)
seen_labels[label] = seen_labels.get(label, 0) + 1
if seen_labels[label] > 1:
label = f"{label} {seen_labels[label]}"
state.camera_labels[camera.name] = label
selector.options = [
ft.DropdownOption(key=c.name, text=state.camera_labels[c.name])
for c in state.cameras
]
if selector.value and selector.value not in state.camera_labels:
selector.value = None
status.value = "Select a camera"
page.update()
def sync_action_buttons():
record_btn.selected = state.is_recording
pause_recording_btn.selected = state.is_recording_paused
pause_recording_btn.disabled = not state.is_recording
stream_btn.selected = state.is_streaming
stream_btn.visible = state.is_streaming_supported
preview_btn.selected = not state.is_preview_paused
async def init_camera(e=None):
state.selected_camera = next(
(c for c in state.cameras if c.name == selector.value),
None,
)
if not state.selected_camera:
status.value = "No camera selected"
return
selected_label = state.camera_labels.get(
state.selected_camera.name, state.selected_camera.name
)
status.value = f"Initializing {selected_label}"
status.update()
await preview.initialize(
description=state.selected_camera,
resolution_preset=fc.ResolutionPreset.MEDIUM,
enable_audio=True,
image_format_group=fc.ImageFormatGroup.JPEG,
)
state.is_streaming = False
state.is_streaming_supported = await preview.supports_image_streaming()
sync_action_buttons()
page.update()
async def take_photo():
data = await preview.take_picture()
last_image.src = data
last_image.update()
async def start_recording():
await preview.prepare_for_video_recording()
await preview.start_video_recording()
state.is_recording = True
state.is_recording_paused = False
sync_action_buttons()
status.value = "Recording video..."
page.update()
async def pause_recording():
await preview.pause_video_recording()
state.is_recording_paused = True
sync_action_buttons()
status.value = "Recording paused"
page.update()
async def resume_recording():
await preview.resume_video_recording()
state.is_recording_paused = False
sync_action_buttons()
status.value = "Recording resumed"
page.update()
async def stop_recording():
data = await preview.stop_video_recording()
state.is_recording = False
state.is_recording_paused = False
sync_action_buttons()
if not data:
status.value = "No video data returned"
page.update()
return
ext = detect_video_extension(data)
filename = f"recording_{datetime.now().strftime('%Y%m%d_%H%M%S')}.{ext}"
saved_path = await ft.FilePicker().save_file(
file_name=filename,
src_bytes=data,
)
kb_size = len(data) / 1024
status.value = f"Video recorded: {kb_size:.1f} KB"
if saved_path:
recorded_video_path.value = f"Recorded video: {saved_path}"
else:
recorded_video_path.value = "Recorded video save canceled"
page.update()
async def on_state_change(e: ft.Event[fc.CameraState]):
if e.description == state.selected_camera:
state.is_recording = e.is_recording_video
state.is_recording_paused = e.is_recording_paused
state.is_streaming = e.is_streaming_images
state.is_preview_paused = e.is_preview_paused
sync_action_buttons()
if e.has_error:
status.value = f"Camera error: {e.error_description}"
elif e.is_taking_picture:
status.value = "Taking picture..."
elif e.is_recording_paused:
status.value = "Recording paused"
elif e.is_recording_video:
status.value = "Recording video..."
elif e.is_streaming_images:
status.value = "Streaming images..."
elif e.is_preview_paused:
status.value = "Preview paused"
else:
status.value = "Camera ready"
page.update()
preview.on_state_change = on_state_change
selector.on_select = init_camera
async def start_streaming():
if not state.is_streaming_supported:
status.value = "Image streaming is not supported by this camera"
page.update()
return
await preview.start_image_stream()
state.is_streaming = True
sync_action_buttons()
page.update()
async def stop_streaming():
await preview.stop_image_stream()
state.is_streaming = False
sync_action_buttons()
page.update()
def on_stream_image(e: ft.Event[fc.CameraImage]):
try:
last_image.src = e.bytes
last_image.update()
except Exception as ex:
logging.exception("Failed to render stream frame: %s", ex)
preview.on_stream_image = on_stream_image
async def pause_preview():
await preview.pause_preview()
state.is_preview_paused = True
sync_action_buttons()
page.update()
async def resume_preview():
await preview.resume_preview()
state.is_preview_paused = False
sync_action_buttons()
page.update()
async def toggle_recording():
if state.is_recording:
await stop_recording()
else:
await start_recording()
async def toggle_recording_pause():
if state.is_recording_paused:
await resume_recording()
else:
await pause_recording()
async def toggle_streaming():
if state.is_streaming:
await stop_streaming()
else:
await start_streaming()
async def toggle_preview():
if state.is_preview_paused:
await resume_preview()
else:
await pause_preview()
take_photo_btn.on_click = take_photo
record_btn.on_click = toggle_recording
pause_recording_btn.on_click = toggle_recording_pause
stream_btn.on_click = toggle_streaming
preview_btn.on_click = toggle_preview
page.on_connect = get_cameras
page.add(
ft.SafeArea(
content=ft.Column(
[
ft.Row(
[
selector,
ft.IconButton(ft.Icons.REFRESH, on_click=get_cameras),
],
wrap=True,
),
ft.Container(
height=320,
bgcolor=ft.Colors.BLACK,
border_radius=3,
content=preview,
),
status,
ft.Row(
[
take_photo_btn,
record_btn,
pause_recording_btn,
stream_btn,
preview_btn,
],
wrap=True,
),
recorded_video_path,
ft.Text("Last photo"),
last_image,
]
)
)
)
await get_cameras()
if __name__ == "__main__":
ft.run(main)
Description#
Inherits: LayoutControl
A control that provides camera preview and capture capabilities.
Properties
-
content(Control | None) –Optional child to overlay on top of the camera preview.
-
preview_enabled(bool) –Whether the preview surface is shown.
Events
-
on_state_change(EventHandler[CameraState] | None) –Fires when the camera controller state changes.
-
on_stream_image(EventHandler[CameraImage] | None) –Fires when an image frame is available while streaming.
Methods
-
get_available_cameras–Lists the available camera devices on the current platform.
-
get_exposure_offset_step_size–Returns the smallest increment supported for exposure offset changes.
-
get_max_exposure_offset–Maximum exposure offset supported by the current camera.
-
get_max_zoom_level–Maximum zoom level supported by the current camera.
-
get_min_exposure_offset–Minimum exposure offset supported by the current camera.
-
get_min_zoom_level–Minimum zoom level supported by the current camera.
-
initialize–Initializes a new camera controller for the given description.
-
lock_capture_orientation–Locks capture orientation to the specified device orientation.
-
pause_preview–Pauses the camera preview.
-
pause_video_recording–Pauses an active video recording.
-
prepare_for_video_recording–Prepares the capture session for video recording.
-
resume_preview–Resumes the camera preview.
-
resume_video_recording–Resumes a paused video recording.
-
set_description–Switches to another camera description.
-
set_exposure_mode–Changes the exposure mode.
-
set_exposure_offset–Sets exposure offset in EV units.
-
set_exposure_point–Sets the exposure metering point.
-
set_flash_mode–Changes the flash mode.
-
set_focus_mode–Changes the focus mode.
-
set_focus_point–Sets the focus metering point.
-
set_zoom_level–Applies the provided zoom level.
-
start_image_stream–Begins streaming camera image frames.
-
start_video_recording–Starts capturing video.
-
stop_image_stream–Stops streaming camera image frames.
-
stop_video_recording–Stops video recording and returns the recorded bytes.
-
supports_image_streaming–Indicates whether image streaming is supported on the current platform.
-
take_picture–Captures a still image and returns the encoded bytes.
-
unlock_capture_orientation–Unlocks the capture orientation.
Properties#
content
class-attribute
instance-attribute
#
content: Control | None = None
Optional child to overlay on top of the camera preview.
preview_enabled
class-attribute
instance-attribute
#
preview_enabled: bool = True
Whether the preview surface is shown.
Events#
on_state_change
class-attribute
instance-attribute
#
on_state_change: EventHandler[CameraState] | None = None
Fires when the camera controller state changes.
on_stream_image
class-attribute
instance-attribute
#
on_stream_image: EventHandler[CameraImage] | None = None
Fires when an image frame is available while streaming.
Methods#
get_available_cameras
async
#
get_available_cameras() -> list[CameraDescription]
Lists the available camera devices on the current platform.
Returns:
-
list[CameraDescription]–A list of available camera descriptions.
get_exposure_offset_step_size
async
#
get_exposure_offset_step_size() -> float
Returns the smallest increment supported for exposure offset changes.
get_max_exposure_offset
async
#
get_max_exposure_offset() -> float
Maximum exposure offset supported by the current camera.
get_max_zoom_level
async
#
get_max_zoom_level() -> float
Maximum zoom level supported by the current camera.
get_min_exposure_offset
async
#
get_min_exposure_offset() -> float
Minimum exposure offset supported by the current camera.
get_min_zoom_level
async
#
get_min_zoom_level() -> float
Minimum zoom level supported by the current camera.
initialize
async
#
initialize(
description: CameraDescription,
resolution_preset: ResolutionPreset,
enable_audio: bool = True,
fps: int | None = None,
video_bitrate: int | None = None,
audio_bitrate: int | None = None,
image_format_group: ImageFormatGroup | None = None,
)
Initializes a new camera controller for the given description.
Parameters:
-
description(CameraDescription) –Camera device to bind to.
-
resolution_preset(ResolutionPreset) –Desired resolution preset.
-
enable_audio(bool, default:True) –Whether audio is enabled for recordings.
-
fps(int | None, default:None) –Optional target frames per second.
-
video_bitrate(int | None, default:None) –Optional video bitrate.
-
audio_bitrate(int | None, default:None) –Optional audio bitrate.
-
image_format_group(ImageFormatGroup | None, default:None) –Optional image format group override.
lock_capture_orientation
async
#
Locks capture orientation to the specified device orientation.
Parameters:
-
orientation(DeviceOrientation | None, default:None) –Specific orientation to lock, or current
prepare_for_video_recording
async
#
Prepares the capture session for video recording.
set_description
async
#
set_description(description: CameraDescription)
Switches to another camera description.
Parameters:
-
description(CameraDescription) –Camera to switch to.
set_exposure_mode
async
#
Changes the exposure mode.
Parameters:
-
mode(ExposureMode) –Exposure mode to apply.
set_exposure_offset
async
#
set_exposure_point
async
#
set_exposure_point(point: OffsetValue | None)
Sets the exposure metering point.
Parameters:
-
point(OffsetValue | None) –Normalized offset (0..1) or None to reset.
set_flash_mode
async
#
Changes the flash mode.
Parameters:
-
mode(FlashMode) –Flash mode to apply.
set_focus_mode
async
#
Changes the focus mode.
Parameters:
-
mode(FocusMode) –Focus mode to apply.
set_focus_point
async
#
set_focus_point(point: OffsetValue | None)
Sets the focus metering point.
Parameters:
-
point(OffsetValue | None) –Normalized offset (0..1) or None to reset.
supports_image_streaming
async
#
supports_image_streaming() -> bool
Indicates whether image streaming is supported on the current platform.