TurboWarp-Camera-Source

TurboWarp capability extension

Named cameras for pose, image, and QR inputs.

TurboWarp-Camera-Source owns `navigator.mediaDevices.getUserMedia()` and shares live frame sources by role name, so TM can use a horizontal camera while jsQR or image recognition uses a downward camera.

start shared camera posestart shared camera qrcamera device ID at 1

Blocks

start shared camera

Requests camera permission and starts or keeps a named MediaStream such as `pose` or `qr`.

show / hide camera preview

Controls the opt-in GPU-backed stage preview. Repeated show calls keep one block-owned lease per camera.

refresh camera devices

Updates the browser camera list so projects can bind role names to device IDs.

shared camera is running?

Reports whether a named stream is active. The device-ID reporter identifies the selected physical camera when available.

camera error

Reports the latest failure code and message for each named camera, and clears them after a successful start.

frame input reporters

Reports the delivered width, height, and track frame rate. Unavailable values and stopped cameras report zero.

API capability

Unsandboxed extensions can read `Scratch.vm.runtime.ext_kubohiroyacamerasource`.

const poseLease = await cameraSource.acquireCamera({
  owner: 'tm',
  cameraId: 'pose',
  deviceId: poseDeviceId
});
const qrLease = await cameraSource.acquireCamera({
  owner: 'jsqr',
  cameraId: 'qr',
  deviceId: qrDeviceId
});
const previewLease = await cameraSource.acquireCamera({
  owner: 'camera-preview',
  cameraId: 'pose',
  preview: true,
  mirrored: true
});
const frame = qrLease.getFrameSource();
// frame.element is an HTMLVideoElement.
await qrLease.release();
await previewLease.release();
await poseLease.release();

Each named stream remains active until its last lease is released. Consumers that use the same `cameraId` share one physical camera; different IDs can run separate cameras.

preview: true enables the dedicated GPU-backed stage preview for that lease. It sends the shared video element to texImage2D(video) without drawImage(), getImageData(), or CPU pixel scanning. Mirroring changes only the drawable scale, leaving frame.element unmodified for WebGPU, WebCodecs, and vision consumers.

The path avoids explicit CPU readback but does not guarantee browser-internal zero-copy. The browser may still perform color conversion or GPU transfer.

Block-owned previews are cleaned up when the named camera stops, the project stops or reloads, or the runtime is disposed.

Privacy and requirements

Camera access requires user permission and a secure browser context such as HTTPS or localhost. Camera Source does not upload frames or store images.