# camera-remote-web-api

TypeScript/JavaScript SDK for controlling Sony cameras via REST, WebSocket live view, and Server-Sent Events. Built on the [Sony Camera Remote SDK](https://support.d-imaging.sony.co.jp/app/sdk/en/index.html) V2.01.00.

## Install

```bash
npm init -y && npm pkg set type=module
npm install camera-remote-web-api
```

This package is ESM-only. The `type=module` step is required for projects using the default CommonJS setting from `npm init`.

npm automatically installs the correct native binary for your platform (macOS ARM64, Linux x64/ARM64, Windows x64).

## Quick Start

```typescript
import { CameraManager } from 'camera-remote-web-api/server';

const manager = new CameraManager({ port: 8080 });

manager.on('camera-ready', async ({ cameraId }) => {
  const cam = manager.camera(cameraId);
  await cam.triggerShutter();
});

await manager.start();
// Binary boots → cameras discovered → auto-connected → 'camera-ready' fires
```

## CameraManager

Starts the native binary, discovers cameras, auto-connects, and handles reconnection — all in one class. Replaces the manual `ServerManager` + `CameraClient` + `EventStream` workflow.

### Server (Node.js)

```typescript
import { CameraManager } from 'camera-remote-web-api/server';

const manager = new CameraManager({
  port: 8080,
  autoPort: true,              // Find next available port if busy
  autoConnect: true,           // Connect cameras on discovery (default)
  connectionMode: 'remote',   // 'remote' | 'remote-transfer' | 'contents'
  autoReconnect: true,         // Reconnect on unexpected disconnect (default)
  maxReconnectAttempts: 5,     // Give up after N attempts (default)
  reconnectTimeout: 30000,     // Wait for SDK auto-reconnect before retrying (ms, default)
  pollInterval: 5000,          // Camera discovery interval in ms (default)
});

await manager.start();

// Use manager.camera(id) for all operations — ID is pre-bound
const cam = manager.camera(id);
await cam.setProperty('iso', { value: '400' });
await cam.triggerShutter();

// Or use manager.client directly with explicit IDs
await manager.client.triggerShutter(id);

await manager.close();
```

### Browser

Connects to an already-running server. Use in React, Next.js client components, or Electron renderers.

```typescript
import { CameraManager } from 'camera-remote-web-api';

const manager = new CameraManager({
  baseUrl: 'http://localhost:8080',
});

manager.on('camera-ready', ({ cameraId, camera }) => {
  console.log(`${camera.model} ready`);
});

manager.start();
```

### Events

| Event | Payload | When |
|-------|---------|------|
| `camera-found` | `{ camera }` | New camera detected |
| `camera-lost` | `{ camera }` | Camera unplugged/offline |
| `camera-connecting` | `{ cameraId }` | Connection attempt started |
| `camera-ready` | `{ cameraId, camera }` | Connected and ready |
| `camera-disconnected` | `{ cameraId, error? }` | Disconnected |
| `connection-failed` | `{ cameraId, error, attempt }` | Connection failed |
| `error` | `{ message }` | Manager-level error |

### Bound Camera

`manager.camera(id)` returns a bound camera object with the ID pre-applied to all methods. Cached per ID.

```typescript
manager.on('camera-ready', async ({ cameraId }) => {
  const cam = manager.camera(cameraId);

  // Actions — no ID needed
  await cam.triggerShutter();
  await cam.setProperty('iso', { value: '400' });
  await cam.controlZoom({ direction: 'in', speed: 'normal' });

  // Per-camera SSE events via cam.events
  cam.events.on('propertyChanged', (data) => {
    console.log('Changed:', data.codes);
  });

  cam.events.on('downloadComplete', (data) => {
    console.log('Saved:', data.filename);
  });

  // One-shot listener — auto-removes after firing
  cam.events.once('transferProgress', (data) => {
    console.log('Transfer done:', data.filename);
  });
});
```

All `CameraClient` methods are available on the bound camera without the `id` parameter. The `events` accessor returns a typed, per-camera SSE stream (lazily created, cached, cleaned up on `close()` or camera-lost).

### Per-Camera SSE Events

Available via `cam.events` or `manager.events(cameraId)`:

| Event | Description |
|-------|-------------|
| `connected` | Camera connection established |
| `disconnected` | Camera disconnected |
| `propertyChanged` | Property values changed |
| `warning` | Camera warning/notification |
| `afStatus` | Autofocus state change |
| `downloadComplete` | Auto-transferred image saved (remote mode) |
| `transferProgress` | File pull completed (remote-transfer mode) |
| `error` | SDK error occurred |
| `close` | Stream closed by manager (camera lost or manager closed) |

All events are fully typed — autocomplete works on event names and callback data.

For advanced use cases, you can also create standalone `EventStream` instances directly — see [EventStream](#eventstream) below.

### State Machine

```
detected → connecting → connected
   ↑           ↓            ↓ (unexpected disconnect)
   └── connection-failed  disconnected → autoReconnect → detected
```

Backoff: 2s, 4s, 6s... capped at 30s. Resets on successful connection.

### Multi-Camera

```typescript
manager.on('camera-ready', async ({ cameraId }) => {
  const cam = manager.camera(cameraId);
  await cam.setPriorityKey({ setting: 'pc-remote' });
});

// Trigger all connected cameras
const connected = manager.getCameras().filter(c => c.state === 'connected');
await Promise.all(connected.map(c => manager.camera(c.info.id).triggerShutter()));
```

### Manual Control

```typescript
const manager = new CameraManager({ autoConnect: false });

manager.on('camera-found', async ({ camera }) => {
  if (camera.model === 'ILCE-9M3') {
    await manager.connect(camera.id, { mode: 'remote' });
  }
});

await manager.disconnect(id);
```

## Direct API Access (Advanced)

For full control without managed state, use the lower-level classes directly.

### CameraClient

Typed HTTP client for all REST endpoints.

```typescript
import { CameraClient } from 'camera-remote-web-api';

const client = new CameraClient('http://localhost:8080');
```

**Connection**
```typescript
await client.listCameras();
await client.connectCamera(id, { mode: 'remote' });
await client.disconnectCamera(id);
await client.getConnectionStatus(id);
```

**Properties** — GET returns `value` (hex), `formatted` (display string), and `available_values`. SET accepts hex, human-readable strings (`"1/125"`, `"f/5.6"`), or decimal (`"400"`).
```typescript
await client.getProperty(id, 'iso');
await client.setProperty(id, 'iso', { value: '400' });
await client.getAllProperties(id);
await client.setPriorityKey(id, { setting: 'pc-remote' });
```

**Actions**
```typescript
await client.triggerShutter(id);
await client.halfPress(id);
await client.afShutter(id);
await client.controlZoom(id, { direction: 'in', speed: 'normal' });
await client.stopZoom(id);
await client.focusNearFar(id, { step: 3 });
```

**Live View** — HTTP for single frames, WebSocket for continuous ~15fps JPEG streaming. OSD overlay composites the camera UI onto the live view.
```typescript
await client.enableLiveView(id);
await client.startLiveViewStream(id);
const frame = await client.getLiveViewFrame(id);  // ArrayBuffer (JPEG)
await client.stopLiveViewStream(id);
```

**SD Card** — requires `remote-transfer` or `contents` connection mode.
```typescript
const files = await client.listSDCardFiles(id, 1);  // slot 1
await client.downloadSDCardFile(id, 1, contentId, fileId);
```

**Settings**
```typescript
await client.getSaveInfo(id);
await client.setSaveInfo(id, { path: '/tmp/photos', prefix: 'IMG_', startNo: 1 });
```

### EventStream

Real-time camera callbacks via Server-Sent Events. Most users should use [`manager.events(cameraId)`](#per-camera-sse-events) instead — this class is for advanced use cases where you need standalone stream management.

```typescript
import { EventStream } from 'camera-remote-web-api';

const events = new EventStream('http://localhost:8080');

events.on('propertyChanged', (data) => console.log(data.codes));
events.on('downloadComplete', (data) => console.log(data.filename));
events.on('afStatus', (data) => console.log(data.state));
events.on('warning', (data) => console.log(data.code, data.message));

// Specific camera
const camEvents = new EventStream('http://localhost:8080', cameraId);

events.close();
```

| Event | Description |
|-------|-------------|
| `connected` | Camera connection established |
| `disconnected` | Camera disconnected |
| `propertyChanged` | Property values changed |
| `warning` | Camera warning/notification |
| `afStatus` | Autofocus state change |
| `downloadComplete` | Auto-transferred image saved (remote mode) |
| `transferProgress` | File pull completed (remote-transfer mode) |
| `error` | SDK error occurred |

### LiveViewStream

WebSocket client for binary JPEG live view frames (Node.js only).

```typescript
import { LiveViewStream } from 'camera-remote-web-api/server';

const liveView = new LiveViewStream('ws://localhost:8080', cameraId);
liveView.onFrame((jpeg: ArrayBuffer) => {
  // Render JPEG frame (~15fps)
});
liveView.close();
```

### ServerManager

Low-level binary process manager. Use CameraManager instead unless you need manual binary lifecycle control.

```typescript
import { ServerManager } from 'camera-remote-web-api/server';

const server = new ServerManager({ port: 8080, autoPort: true });
await server.start();
await server.isRunning();  // true
server.getPort();          // 8080
await server.stop();
```

## CLI

The `camera-server` CLI is the easiest way to run the camera server without writing any code. It installs with the package and handles the binary lifecycle for you.

### Server management

```bash
npx camera-server start                      # Start the camera server (default port 8080)
npx camera-server start --port 9090          # Start on a specific port
npx camera-server start 9090                 # Shorthand for --port 9090
npx camera-server status                     # Discover and show ALL running servers
npx camera-server status --port 9090         # Status of a specific server
npx camera-server stop                       # Stop ALL running servers
npx camera-server stop --port 9090           # Stop a specific server
```

`start` spawns the `CameraWebApp` binary as a detached background process — it survives after the CLI exits. If the default port is busy, it auto-increments to the next available port. Using `--port N` explicitly targets that port and errors if it's taken.

`stop` first tries a graceful shutdown via the server's API, then falls back to SIGTERM/SIGKILL if needed. It only kills the actual listening process, so any browser tabs connected to the server are left untouched.

`status` discovers all running `CameraWebApp` instances regardless of port, queries each one, and reports version, uptime, and connected camera count.

### Setup and diagnostics

```bash
npx camera-server info                       # Show version and binary info
npx camera-server doctor                     # Check system prerequisites
npx camera-server platforms                  # List available platforms
npx camera-server install [--platform <name>]    # Install platform binary
npx camera-server uninstall [--platform <name>]  # Remove platform binary
npx camera-server docs                       # Open API docs in browser
npx camera-server mcp                        # Configure MCP servers
```

Cross-platform development:

```bash
npx camera-server install --platform linux-x64
```

### Interactive TUI

Run `npx camera-server` with no arguments to launch an interactive terminal UI with autocomplete, command history, and live output:

```bash
npx camera-server
```

Inside the TUI you can type any command (`start`, `stop`, `status`, `info`, etc.) directly. Press **Tab** for autocomplete, **/** to browse the command list, or **Esc** to exit.

### Typical workflow

```bash
npm install camera-remote-web-api    # Install package (binary auto-downloads)
npx camera-server start              # Start the server in the background
npx camera-server status             # Verify it's running
# ... your app connects to http://localhost:8080 ...
npx camera-server stop               # Stop the server when done
```

## Supported Platforms

| Platform | Architecture | Package |
|----------|-------------|---------|
| macOS | Apple Silicon (ARM64) | `@alpha-sdk/darwin-arm64` |
| Linux | x64 | `@alpha-sdk/linux-x64` |
| Linux | ARM64 | `@alpha-sdk/linux-arm64` |
| Windows | x64 | `@alpha-sdk/win32-x64` |

## Connection Modes

| Mode | Description |
|------|-------------|
| `remote` | Full camera control: shooting, settings, live view. Auto-transfers images to host PC. |
| `remote-transfer` | Camera control + explicit SD card file access. Most capable mode. |
| `contents` | SD card file browsing and download only. No shooting or settings control. |

## Compatible Cameras

23 Sony cameras supported via Camera Remote SDK V2.01.00.

### Alpha / Mirrorless

| Model | USB | Wired LAN | Wi-Fi | Remote | Contents Transfer | Remote Transfer |
|-------|-----|-----------|-------|--------|-------------------|-----------------|
| ILCE-1M2 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| ILCE-1 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| ILCE-9M3 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| ILCE-9M2 | ✓ | ✓ | — | ✓ | — | — |
| ILCE-7RM5 | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| ILCE-7RM4A | ✓ | — | — | ✓ | ✓ | — |
| ILCE-7RM4 | ✓ | — | — | ✓ | — | — |
| ILCE-7CR | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| ILCE-7SM3 | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| ILCE-7M5 \*1 | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| ILCE-7M4 | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| ILCE-7CM2 | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| ILCE-7C | ✓ | — | — | ✓ | ✓ | — |
| ILCE-6700 | ✓ | — | ✓ | ✓ | ✓ | ✓ |

### Cinema / FX

| Model | USB | Wired LAN | Wi-Fi | Remote | Contents Transfer | Remote Transfer |
|-------|-----|-----------|-------|--------|-------------------|-----------------|
| ILME-FX3A \*2 | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| ILME-FX3 (v2.00+) \*2 | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| ILME-FX2 | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| ILME-FX30 \*2 | ✓ | — | ✓ | ✓ | ✓ | ✓ |

### Professional

| Model | USB | Wired LAN | Wi-Fi | Remote | Contents Transfer | Remote Transfer |
|-------|-----|-----------|-------|--------|-------------------|-----------------|
| ILX-LR1 \*2 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |

### Vlog / Creator

| Model | USB | Wired LAN | Wi-Fi | Remote | Contents Transfer | Remote Transfer |
|-------|-----|-----------|-------|--------|-------------------|-----------------|
| ZV-E1 | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| ZV-E10M2 | ✓ | — | ✓ | ✓ | ✓ | — |

### Compact

| Model | USB | Wired LAN | Wi-Fi | Remote | Contents Transfer | Remote Transfer |
|-------|-----|-----------|-------|--------|-------------------|-----------------|
| DSC-RX1RM3 | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| DSC-RX0M2 (v3.00+) | ✓ | — | — | ✓ | ✓ | — |

> \*1 Must use SSH authentication. \*2 Supports USB Type-C wired LAN adaptor (Gigabit Ethernet recommended). Wi-Fi not supported on Linux ARMv8.

### OS Compatibility

| Platform | Verified Environments |
|----------|-----------------------|
| Windows | Windows 11 64-bit |
| Linux x64 | Ubuntu 22.04 LTS, Ubuntu 24.04 LTS |
| Linux ARMv8 (64-bit) | JETSON Orin Nano, Jetson Nano B01, Raspberry Pi4 (4GB) |
| macOS | 14 Sonoma, 15 Sequoia, 26 Tahoe |

## Documentation

Full API reference and guides: [crsdk.app/sdk/overview](https://crsdk.app/sdk/overview)

## Requirements

- Node.js >= 18
- Sony camera with Remote SDK support (USB or network)
- Windows: libusbK 3.0 driver for USB connections

## License

This package (TypeScript client, CLI) is licensed under the [MIT License](LICENSE).

The platform binary packages (`@alpha-sdk/*`) include the Sony Camera Remote SDK, which is subject to the [Sony Camera Remote SDK License Agreement](https://support.d-imaging.sony.co.jp/app/sdk/licenseagreement_d/en.html). By installing and using the platform binaries, you agree to Sony's license terms.
