Skip to content
Shiny.NET

Device Camera

NuGet package Shiny.AppDeviceBridge.Camera

The device camera bridge drives this device’s own camera from a page anywhere: a live MJPEG viewfinder, photos and video filed into a file root, lens, zoom, torch and effects.

Terminal window
dotnet add package Shiny.AppDeviceBridge.Camera
builder.UseAppDeviceBridge(
bridge => bridge.AddCameraBridge(),
webApp => { /* … */ }
);
Client C# TypeScript
ICameraBridge AddCameraBridgeClient() CameraBridge

This device’s own camera, driven from a page anywhere — the reason being a phone on a mount, framed and fired from a laptop across the room. Not the camera of the machine showing the page: that’s getUserMedia (see Camera, microphone and location in the page). Android, iOS, Mac Catalyst, macOS and Windows, through Shiny’s CameraView; Linux has no camera and answers 501.

bridge.AddCameraBridge(o =>
{
o.Root = "data"; // captures are filed here…
o.Folder = "camera"; // …as camera/IMG_20260916-201502.jpg, camera/VID_….mov
});
  • A camera screen, not a background service. The camera runs on a view on screen — iOS won’t run one in the background at all — so GET camera says whether one is live, and until it is every command answers 409 camera_not_open. That’s a normal state: show it as “open the camera on the device”, not as an error.
  • Opening it from afar. POST camera/open (202) asks the device to show its camera. Unless something handles CameraBridgeSession.OpenRequested, the bridge pushes its own CameraBridgePage over the web app — shutter, photo and video, flip, torch, close. POST camera/close closes it. For a camera screen of your own, put a CameraBridgeView on it, set PresentWhenOpened = false and navigate there from OpenRequested; the buttons on that screen and the requests from a page run the same methods.
  • Driving it. POST camera/photo and POST/DELETE camera/recording capture at full resolution and file into the file root, answering { file: { root, path }, kind, size, … } for the files bridge. PUT camera/settings is a patch — videoMode, facing, cameraId, active, filter, torchOn, zoom — so two viewers don’t undo each other; changing the camera mid-recording is a 422 camera_state. Phones flip front and back; desktops list cameras and set choosesCamera, since none of theirs is front or back.
  • The viewfinder. GET camera/preview is multipart/x-mixed-replace MJPEG an <img> plays: small (720 px) and 12 frames a second by default (PreviewMaxEdge, PreviewFramesPerSecond, PreviewQuality), because latency matters more than smoothness when framing. The device encodes only while someone watches, and a slow viewer gets the newest frame.
  • Everyone follows the device. camera.status carries the whole status whenever anything changes — somebody picks the phone up and switches to video, and every page’s controls follow.
  • Filing elsewhere. Register an ICameraCaptureStore before AddCameraBridge to file captures through your own storage.
  • Platform setup: NSCameraUsageDescription and NSMicrophoneUsageDescription on Apple platforms (plus the camera and audio-input entitlements where sandboxed), CAMERA and RECORD_AUDIO on Android, the webcam and microphone capabilities on Windows. POST camera/access prompts where the platform will.
<img src="_bridge/camera/preview?t=1726517702" /> <!-- a new query each time it's shown -->
const camera = new CameraBridge();
camera.onStatus(status => render(status));
await camera.open();
const photo = await camera.takePhoto(); // { file: { root: "data", path: "camera/IMG_….jpg" }, … }
await camera.updateSettings({ videoMode: true, zoom: 2 });
GET/_bridge/camera#

The camera's state, and whether this platform has one and the app may use it — without prompting.

C#ICameraBridge.GetStatusAsyncTSCameraBridge.getStatus
Response
200 CameraStatus
CameraStatus · 19 fields
supportedbooleanWhether this platform has a camera to bridge. False on Linux.
accessAccessStateThe camera permission, without prompting.
livebooleanWhether a camera screen is open on the device, so there is something to drive.
activebooleanWhether that screen has the camera turned on. A live camera can be put down without closing it.
videoModebooleanVideo rather than photos. The two are modes, never both at once.
recordingbooleanWhether a video is being recorded.
busybooleanWhether a capture is in progress.
facingCameraFacingWhich way the camera in use points.
cameraIdstring | nullThe camera pinned by id, or null when the device chose by facing.
camerasCameraDevice[] | nullThe cameras this device offers.
choosesCamerabooleanWhether this device picks a camera from cameras rather than flipping between front and back — true on desktops, whose cameras are neither.
torchOnbooleanWhether the torch is lit.
torchAvailablebooleanWhether there is a torch to light. False on front cameras and desktops.
zoomnumberThe zoom factor, between minZoom and maxZoom.
minZoomnumberThe least the camera in use zooms.
maxZoomnumberThe most the camera in use zooms.
filterCameraFilterThe effect in use.
lastCaptureCameraCapture | nullThe most recent capture this camera filed.
messagestring | nullA sentence for people: what the camera is doing, or why it is not.
POST/_bridge/camera/access#

Prompts for camera access where the platform still will, and says where it stands.

C#ICameraBridge.RequestAccessAsyncTSCameraBridge.requestAccess
Response
200 CameraAccessResult
CameraAccessResult · 1 field
accessAccessStateWhere the camera permission stands after asking.
POST/_bridge/camera/open#

Asks the device to show its camera screen. Answers straight away; the camera arrives on onStatus when it is up. Does nothing when one is already open.

C#ICameraBridge.OpenAsyncTSCameraBridge.open
Response
204 No content
POST/_bridge/camera/close#

Closes the camera screen the bridge opened. A camera screen the app opened itself is the app's to close.

C#ICameraBridge.CloseAsyncTSCameraBridge.close
Response
204 No content
POST/_bridge/camera/photo#

Takes a photo at full resolution and files it.

C#ICameraBridge.TakePhotoAsyncTSCameraBridge.takePhoto
Response
200 CameraCapture
CameraCapture · 6 fields
fileBridgeFileWhere it was filed.
kindCameraCaptureKindA photo or a video.
sizenumberBytes.
widthnumber | nullA photo's width in pixels.
heightnumber | nullA photo's height in pixels.
durationSecondsnumber | nullA video's length, where the platform reports one.
POST/_bridge/camera/recording#

Starts recording video. The camera has to be in video mode.

C#ICameraBridge.StartRecordingAsyncTSCameraBridge.startRecording
Response
204 No content
DELETE/_bridge/camera/recording#

Stops recording and files the video.

C#ICameraBridge.StopRecordingAsyncTSCameraBridge.stopRecording
Response
200 CameraCapture
CameraCapture · 6 fields
fileBridgeFileWhere it was filed.
kindCameraCaptureKindA photo or a video.
sizenumberBytes.
widthnumber | nullA photo's width in pixels.
heightnumber | nullA photo's height in pixels.
durationSecondsnumber | nullA video's length, where the platform reports one.
PUT/_bridge/camera/settings#

Changes the live camera, and answers with the state it is in afterwards.

C#ICameraBridge.UpdateSettingsAsyncTSCameraBridge.updateSettings
Body
application/json · CameraSettingsInput
CameraSettingsInput · 7 fields
videoModeboolean | nullSwitch between photos and video. Refused while recording.
facingCameraFacing | nullChoose a camera by which way it points. Refused while recording.
cameraIdstring | nullPin a camera by id, which wins over facing; empty unpins it. Refused while recording.
activeboolean | nullTurn the camera on or off without closing its screen. Refused while recording.
filterCameraFilter | nullThe effect to apply.
torchOnboolean | nullLight the torch, where there is one.
zoomnumber | nullThe zoom factor, clamped to the camera's range.
Response
200 CameraStatus
CameraStatus · 19 fields
supportedbooleanWhether this platform has a camera to bridge. False on Linux.
accessAccessStateThe camera permission, without prompting.
livebooleanWhether a camera screen is open on the device, so there is something to drive.
activebooleanWhether that screen has the camera turned on. A live camera can be put down without closing it.
videoModebooleanVideo rather than photos. The two are modes, never both at once.
recordingbooleanWhether a video is being recorded.
busybooleanWhether a capture is in progress.
facingCameraFacingWhich way the camera in use points.
cameraIdstring | nullThe camera pinned by id, or null when the device chose by facing.
camerasCameraDevice[] | nullThe cameras this device offers.
choosesCamerabooleanWhether this device picks a camera from cameras rather than flipping between front and back — true on desktops, whose cameras are neither.
torchOnbooleanWhether the torch is lit.
torchAvailablebooleanWhether there is a torch to light. False on front cameras and desktops.
zoomnumberThe zoom factor, between minZoom and maxZoom.
minZoomnumberThe least the camera in use zooms.
maxZoomnumberThe most the camera in use zooms.
filterCameraFilterThe effect in use.
lastCaptureCameraCapture | nullThe most recent capture this camera filed.
messagestring | nullA sentence for people: what the camera is doing, or why it is not.
EVENTcamera.status#

The camera changed — opened, closed, switched mode, filed a capture. Raised with the whole state.

C#ICameraBridge.OnStatusAsyncTSCameraBridge.onStatus
Payload
CameraStatus
supportedbooleanWhether this platform has a camera to bridge. False on Linux.
accessAccessStateThe camera permission, without prompting.
livebooleanWhether a camera screen is open on the device, so there is something to drive.
activebooleanWhether that screen has the camera turned on. A live camera can be put down without closing it.
videoModebooleanVideo rather than photos. The two are modes, never both at once.
recordingbooleanWhether a video is being recorded.
busybooleanWhether a capture is in progress.
facingCameraFacingWhich way the camera in use points.
cameraIdstring | nullThe camera pinned by id, or null when the device chose by facing.
camerasCameraDevice[] | nullThe cameras this device offers.
choosesCamerabooleanWhether this device picks a camera from cameras rather than flipping between front and back — true on desktops, whose cameras are neither.
torchOnbooleanWhether the torch is lit.
torchAvailablebooleanWhether there is a torch to light. False on front cameras and desktops.
zoomnumberThe zoom factor, between minZoom and maxZoom.
minZoomnumberThe least the camera in use zooms.
maxZoomnumberThe most the camera in use zooms.
filterCameraFilterThe effect in use.
lastCaptureCameraCapture | nullThe most recent capture this camera filed.
messagestring | nullA sentence for people: what the camera is doing, or why it is not.

JSON property names are camelCase and enums travel as their names. A ? marks a property the payload may omit;| null one that may be null.

CameraStatusobject#

The camera as it is right now. Everything about the camera itself is meaningless while live is false: no camera screen is open on the device, and every command answers 409.

supportedbooleanWhether this platform has a camera to bridge. False on Linux.
accessAccessStateThe camera permission, without prompting.
livebooleanWhether a camera screen is open on the device, so there is something to drive.
activebooleanWhether that screen has the camera turned on. A live camera can be put down without closing it.
videoModebooleanVideo rather than photos. The two are modes, never both at once.
recordingbooleanWhether a video is being recorded.
busybooleanWhether a capture is in progress.
facingCameraFacingWhich way the camera in use points.
cameraIdstring | nullThe camera pinned by id, or null when the device chose by facing.
camerasCameraDevice[] | nullThe cameras this device offers.
choosesCamerabooleanWhether this device picks a camera from cameras rather than flipping between front and back — true on desktops, whose cameras are neither.
torchOnbooleanWhether the torch is lit.
torchAvailablebooleanWhether there is a torch to light. False on front cameras and desktops.
zoomnumberThe zoom factor, between minZoom and maxZoom.
minZoomnumberThe least the camera in use zooms.
maxZoomnumberThe most the camera in use zooms.
filterCameraFilterThe effect in use.
lastCaptureCameraCapture | nullThe most recent capture this camera filed.
messagestring | nullA sentence for people: what the camera is doing, or why it is not.
AccessStateenum#

Where a permission stands. Reported by every bridge that needs one; the names match Shiny's own.

"Unknown""NotSupported""NotSetup""Disabled""Restricted""Denied""Available"
CameraFacingenum#

Which way a camera points. Most desktop cameras are External, which is neither front nor back.

"Back""Front""External"
CameraDeviceobject#

One camera the device can open.

idstringWhat cameraId takes.
namestringThe name the platform gives it — "FaceTime HD Camera", "Back Camera".
facingCameraFacingWhich way it points.
isDefaultbooleanWhether the platform would choose it when nothing is pinned.
CameraFilterenum#

The camera's built-in effects, applied to the preview, the viewfinder stream and the captures alike.

"None""Mono""Noir""Sepia""Invert""Vivid""Cool""Warm""Fade""Chrome""Instant""Tonal"
CameraCaptureobject#

A capture, filed into a file root and read through the files bridge.

fileBridgeFileWhere it was filed.
kindCameraCaptureKindA photo or a video.
sizenumberBytes.
widthnumber | nullA photo's width in pixels.
heightnumber | nullA photo's height in pixels.
durationSecondsnumber | nullA video's length, where the platform reports one.
BridgeFileobject#

A file the page can reach through the files bridge: a named root and a path inside it. Bridges that take or return a file use this, so nothing reaches the page that /_bridge/files would not also serve.

rootstring
pathstring
CameraCaptureKindenum#
"Photo""Video"
CameraAccessResultobject#
accessAccessStateWhere the camera permission stands after asking.
CameraSettingsInputobject#

A change to the live camera. Every member is optional and null leaves it alone — a patch, so two viewers of the same camera do not undo each other's changes.

videoMode?boolean | nullSwitch between photos and video. Refused while recording.
facing?CameraFacing | nullChoose a camera by which way it points. Refused while recording.
cameraId?string | nullPin a camera by id, which wins over facing; empty unpins it. Refused while recording.
active?boolean | nullTurn the camera on or off without closing its screen. Refused while recording.
filter?CameraFilter | nullThe effect to apply.
torchOn?boolean | nullLight the torch, where there is one.
zoom?number | nullThe zoom factor, clamped to the camera's range.

These routes sit under AppDeviceBridgePolicies.Bridges like every other bridge: callers on this device by default. See Security.