# `PhxDemo.Camera`
[🔗](https://github.com/thenatespack/phoenix_demo/blob/main/lib/phx_demo/camera.ex#L1)

Simulated camera for demos (the machinery behind `PhxDemo.Steps.use_camera/3`,
`use_camera_image/2` and `stop_camera/1`).

A JS stand-in (`priv/camera.js`) replaces `navigator.mediaDevices.getUserMedia`
for video requests. It draws a hidden `<video>` or `<img>` onto a canvas and
hands out `canvas.captureStream()`. The same stand-in serves the simulated
microphone (see `PhxDemo.Audio`), so a `{video, audio}` request gets both fake
tracks. Because every stream comes from the same
canvas, switching fixtures between steps changes what an app that is already
streaming sees.

The fixture bytes reach the page through a Playwright route on the app's own
origin (`/__phx_demo_camera/...`), so there is no CORS or canvas tainting, and
the choice of fixture survives full page loads. The route is answered by
Playwright, so requests never reach your app. A strict CSP is unaffected as
long as it allows `'self'` for `connect-src` and `media-src`/`img-src` `blob:`.

## Formats

Images (png, jpg, gif, webp, bmp, svg) and `.webm`/`.ogv` videos are served
as they are. Playwright's Chromium has no H.264, so any other video (mp4,
mov, mkv, ...) is converted with ffmpeg to VP8 WebM at the viewport size and
30 fps, without audio. Conversions are cached under the system temp dir,
keyed by the file's path, size, mtime and the viewport.

## Limits

Screen sharing (`getDisplayMedia`) is not simulated.

# `check_fixtures!`

Raises with a clear message if a fixture is missing, or needs ffmpeg and it
isn't installed. Used by `PhxDemo.Env` (pre-flight) and by the steps.

# `classify`

How a fixture will be served: `:image`, `:video` (as is) or `:convert` (via ffmpeg).

# `convert!`

Converts a video to VP8 WebM at `viewport` size (aspect ratio preserved, padded)
and 30 fps, cached. Returns the converted path.

# `js`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
