Take one photo, or record one audio clip, through the HOST's capture UI
(BROWSER_CAPABILITIES_SPEC §3 grade 1).
The host opens the device at its own origin, draws the viewfinder, and hands you
the bytes when the user presses Done. You are never in the loop while the device
is live — that is the point of the design, not a limitation of it:
The capture UI is drawn by the host, so a user can trust it the way they trust
the sign-in dialog. Do not build a lookalike; imitating host chrome is spoofing.
While the device is open the host shows a persistent "camera on" / "microphone
on" indicator in its own chrome, for the whole session. You cannot hide it and
should not try to.
If the user cancels, this rejects cancelled and nothing was recorded. There
is no partial result — do not write error handling that hopes for one.
try { const { bytes, mimeType } = awaitcapturePhoto({ facing:'environment' }); constmount = awaitrequestMount(); // the user picks a space to save into constfs = openFs(mount); // writeFile is a METHOD on the MountFs awaitfs.mkdir('photos', { recursive:true }); // writeFile does not create parents awaitfs.writeFile(`photos/latest.${mimeType==='image/png'?'png':'jpg'}`, newUint8Array(bytes)); } catch (e) { if ((eas { code?: string }).code === 'cancelled') return; // the user said no throwe; }
(The extension follows mimeType because the HOST chooses the format — do not assume
JPEG. openAppFs() is the variant that writes to your own app space with no pick.)
Two things must BOTH be true, exactly as for any other task:
your app holds task:invokeanddevice:camera (or device:microphone)
— declare them in immediately.run.capabilities so the user can grant them.
Rejects with .code: cancelled (the user dismissed the capture — nothing was
recorded), forbidden (you lack the capability; message: 'browser-denied' means
the BROWSER refused immediately.run itself, which your consent cannot fix),
unavailable (no such device on this machine, or it would not start),
unsupported (this host cannot capture), not-declared, timeout.
Zero-platform alternative, still worth knowing:<input type="file" accept="image/*" capture="environment"> opens the OS camera from inside the
sandbox (file choosers are not permission-gated). It needs no capability and no
host support; it also gives you no indicator and no host-drawn surface.
Take one photo, or record one audio clip, through the HOST's capture UI (
BROWSER_CAPABILITIES_SPEC§3 grade 1).The host opens the device at its own origin, draws the viewfinder, and hands you the bytes when the user presses Done. You are never in the loop while the device is live — that is the point of the design, not a limitation of it:
cancelledand nothing was recorded. There is no partial result — do not write error handling that hopes for one.(The extension follows
mimeTypebecause the HOST chooses the format — do not assume JPEG.openAppFs()is the variant that writes to your own app space with no pick.)Two things must BOTH be true, exactly as for any other task:
immediately.run.invokeslistscapture-photo(orcapture-audio);task:invokeanddevice:camera(ordevice:microphone) — declare them inimmediately.run.capabilitiesso the user can grant them.Rejects with
.code:cancelled(the user dismissed the capture — nothing was recorded),forbidden(you lack the capability;message: 'browser-denied'means the BROWSER refused immediately.run itself, which your consent cannot fix),unavailable(no such device on this machine, or it would not start),unsupported(this host cannot capture),not-declared,timeout.Zero-platform alternative, still worth knowing:
<input type="file" accept="image/*" capture="environment">opens the OS camera from inside the sandbox (file choosers are not permission-gated). It needs no capability and no host support; it also gives you no indicator and no host-drawn surface.