Osy#betaa language · its runtime Osyrin · a hosted platform
Why Osy#Built for agentsAgents as declarationsWorkflows that waitRuns exactly onceSecure by defaultNothing to mockThe editor is the compilerUI in the languageDocuments are dataOne program

Reference / UI

camera and microphone

Camera.Start(facing: Back, withSound: true) · Camera.Capture() · Camera.Record() / StopRecording() · Mic.Start() / Record() / StopRecording() / Stop()

Take a photograph, record a video, or record audio. `Camera.Start()` asks for the device and `Camera()` shows the viewfinder; `Camera.Capture()` answers an `UploadedFile` — the same value the `Upload` control hands over, so a photograph is stored exactly like a chosen file. Every refusal is one catchable `DeviceUnavailableException`.

preview1 example compiled by CIuimediacapturedevice

Summary#

Two devices, one shape. Camera and Mic each have a device pair and a recording pair:

acquire / releaserecord
cameraCamera.Start(facing: Back) · Camera.Stop()Camera.Record() · Camera.StopRecording()
microphoneMic.Start() · Mic.Stop()Mic.Record() · Mic.StopRecording()

The camera also takes still photographs with Camera.Capture(), and shows what it sees through the Camera() atom — the viewfinder.

Everything that produces a file — Capture, and both StopRecordings — answers an UploadedFile, which is the same value the upload control hands an onUploaded action. So a photograph and a chosen file are stored the same way, by the same code.

Signature#

Camera.Start(facing: CameraFacing.Back)        // ask for the camera; CameraFacing is Any (default), Front or Back
Camera.Start(withSound: true)                  // …and the microphone, if this app will record video WITH sound
Camera.Stop()                                  // release it

UploadedFile photo = Camera.Capture()          // photograph the viewfinder
Camera.Record()                                // start recording
UploadedFile clip = Camera.StopRecording()     // stop, and answer the whole clip

Mic.Start()                                    // ask for the microphone
Mic.Record()
UploadedFile note = Mic.StopRecording()
Mic.Stop()

Description#

A whole photo booth#

using Osysharp.Storage;

[Page("/booth")] [AllowAnonymous]
component Booth() {
  bool live = false;
  string problem = "";
  string shot = "";

  action Begin() {
    try {
      Camera.Start(facing: CameraFacing.Front);
      live = true;
      problem = "";
    } catch (DeviceUnavailableException ex) {
      problem = ex.Message;
    }
  }

  action Snap() {
    try {
      UploadedFile photo = Camera.Capture();
      shot = photo.Path;
    } catch (DeviceUnavailableException ex) {
      problem = ex.Message;
    }
  }

  action Done() { Camera.Stop(); live = false; }

  render {
    Stack(gap: 3) {
      if (!live) { Pressable("Turn the camera on", onClick: Begin); }
      if (live) {
        Camera();
        Row(gap: 2) {
          Pressable("Take a photo", onClick: Snap);
          Pressable("Done", onClick: Done);
        }
      }
      if (shot != "") { Image(shot); }
      if (problem != "") { Text(problem); }
    }
  }
}

Being refused is normal, and it is one exception#

A person saying no to a permission prompt is the system working, not a failure. So every capture verb refuses the same way — DeviceUnavailableException — and an app that uses both devices writes one catch.

ex.Message is a sentence you can show, and it says which of four things happened:

what happenedwhat the person can do
they refused the promptchange it from the padlock in the address bar
the machine has no camera or microphonenothing — hide the feature
another application is holding the deviceclose the other application
the page is not on https or localhostnothing they can do — see below

The last one is a deployment problem, not an answer. A browser hands out a camera or microphone only on https:// or localhost, and there is no fallback of any kind — unlike Clipboard, which has a second path for exactly this case. An app served over plain http:// on a LAN address cannot have a camera. Nothing at compile time can see that, which is why it arrives as this exception rather than as a refusal to build.

The device and the recording are separate#

Start/Stop hold the device. Record/StopRecording are one clip. They are separate because holding the microphone across several recordings is the normal case, and re-acquiring it per clip would prompt every time.

Mic.Start();                          // one prompt
Mic.Record();  UploadedFile a = Mic.StopRecording();
Mic.Record();  UploadedFile b = Mic.StopRecording();
Mic.Stop();                           // the indicator goes out here

Stop() always means the device and StopRecording() always means the clip, on both ambients — even though Mic.Stop() could have meant "stop recording", since a microphone has nothing to show. One vocabulary is worth more than a locally shorter name.

Sound is asked for when you start, not when you record#

Camera.Start() acquires video only. To record video with sound, say so up front:

Camera.Start(facing: Back, withSound: true);

Acquiring the microphone later, at Camera.Record(), would show a second permission prompt at the moment somebody presses record — the worst possible time. And an app that only takes photographs should not be asking for a microphone at all. One prompt, for exactly what you declared.

The viewfinder#

Camera() is a live <video>. It takes no props of its own — which camera, and when, is Camera.Start's business, because that is the call that can be refused. Style it like any other element.

A component's verbs act on its own Camera(), the same rule Canvas follows: a viewfinder inside a nested component belongs to that component, and the one that starts the camera must be the one that declares it.

Playing it back#

Video and Audio play a file the app has stored — a recording you just made, or anything else in its file store. Both always show controls.

Video(clip.Path);
Video(clip.Path, poster: still.Path, loop: true);
Audio(note.Path);

What comes out#

A photograph is a JPEG at the camera's own resolution — not the size of the element on screen, so a photo does not change with the page layout.

A recording is whatever the browser records: WebM on Chrome and Firefox, MP4 on Safari. There is no single format every browser produces, so the platform asks for the best each one supports rather than insisting on one and failing on the others. Whatever arrives, the server derives the stored file's real type from its bytes.

Trying it before it exists#

Camera.Capture() refuses if the camera has not produced its first frame yet, rather than storing a blank image. If you photograph immediately after Camera.Start(), expect that refusal — its message says the camera is still starting up.

See also#

  • upload — the Upload control, which hands over the same UploadedFile
  • sound — playing audio the app ships, which is a different thing from recording it
  • Canvas — the drawing surface, and the "a component acts on its own element" rule
  • component — actions, state, and where these verbs are called from

Related

sound

Drop `.mp3`, `.wav`, `.ogg` or `.m4a` files into `model/sounds/` and play them with `Sound.Play(Sounds.Flap)`. The name…

upload

`Upload(onUploaded: Ingest) { … }` is a file picker wearing whatever you put inside it. When someone chooses a file its…

component

The one archetype for all UI: a bounded reactive unit — typed props, reactive members (fields, `live`…

textures

Drop `.png`, `.jpg` or `.webp` files into `model/textures/` and blit them onto a canvas with `Draw.Image(wall, …)`. The…