Skip to main content

Module pipeline

Module pipeline 

Source
Expand description

M-CAM.3 / AUT-257 — camera-pipeline worker thread.

Owns a dedicated OS thread that runs the gst-autovideosrc capture subprocess, pulls BGRA frames into Rust, and (in follow-up commits) uploads each frame to a wisp::VideoTexture, renders the wisp scene with an M-VEC.6 circle mask into an offscreen RenderTexture, reads back the masked BGRA bytes, and emits them to Leptos via a Tauri Channel<T>.

Just the **gst-into-Rust** layer:

1. `start_preview` spawns a [`CameraPipeline`] worker.
2. Worker opens `media::gstreamer_video::VideoStream::from_default_camera`,
   triggering the macOS permission prompt on first run.
3. Worker loops `next_frame()` and advances the
   [`PreviewLifecycle`](crate::preview::PreviewLifecycle) state
   machine — `Starting → Running` on first successful frame.
4. `stop_preview` drops the worker; `Drop` flips the cancel flag
   and joins the thread. The gst child is killed by
   `gstreamer_video::VideoStream`'s own `Drop` impl (per CLAUDE.md
   "Drop-kill the child" pattern).

NOT yet shipped:

* **No wisp upload + render** — the frames sit in the worker; they
  don't yet flow through a `wisp::Stage` + M-VEC.6 mask. That's the
  next commit.
* **No frame emission to Leptos** — Tauri `Channel<T>` is wired
  alongside the wisp work. For now, the user sees the `Running`
  lifecycle transition in `preview_status` but no pixels.

Thread-affinity contract — media::gstreamer_video::VideoStream owns a std::process::Child (gst-launch subprocess) + a stdout reader. Child is Send, so the worker thread can own the stream exclusively. No Rc/RefCell anywhere in the type, so it’s safe to move into the spawned thread.

Structs§

CameraPipeline
Camera-pipeline worker handle. Owns the spawned thread and a cooperative cancel flag; Drop cancels + joins so a panicking caller can never leave a zombie gst child behind.
CameraPipelineHandle
Tauri-managed handle for the active camera pipeline. Held in tauri::State so the stop_preview command can drop the worker (and consequently kill the gst child + join the thread).

Constants§

PREVIEW_FPS
Source framerate request. gst will negotiate the closest the OS camera supports; the actual rate is reflected in [media::gstreamer_video::GstreamerVideoCapture::framerate].
PREVIEW_HEIGHT
Default capture height in pixels. Matches PREVIEW_WIDTH — see that constant’s docs for the square-crop rationale.
PREVIEW_WIDTH
Default capture width in pixels. Paired with PREVIEW_HEIGHT — square dims are the natural input shape for the circular bubble mask. 720×720 (M-QUAL.3): the webcam’s native 16:9 frame is center-cropped to 1:1 then scaled here, so the recorded bubble is crisp at native-resolution output instead of an upscaled 480².

Functions§

advance_to_running 🔒
Mark the preview lifecycle as Running (idempotent — already- Running stays running). Called from the worker thread on each successful frame; the mark_running transition is idempotent so we don’t need a “first frame” guard.
reset_lifecycle 🔒
Drive the lifecycle back to Idle on shutdown OR on gst-side startup failure (no camera attached, permission denied, etc.).
run_pipeline 🔒
The actual worker loop. Lives on the spawned thread; the only reason it’s & borrows is so the public-facing spawn can move the cloned values in without keeping Self alive on the thread.