Multi-camera NDI controller desktop application for managing iPhone cameras running AvoCam.
- mDNS Discovery: Automatically discover cameras on the local network
- Manual Addition: Add cameras by IP address
- Grid View: Monitor multiple cameras simultaneously
- Real-time Telemetry: FPS, bitrate, battery, temperature, WiFi signal
- Group Control: Start/stop streams and adjust settings for multiple cameras at once
- Bounded Concurrency: Parallel operations with semaphore-based rate limiting
- WebSocket: Live telemetry updates from cameras
- Profile Management: (Coming in LOT B) Save and recall camera settings
✅ Complete: Core Rust backend with mDNS, HTTP client, WebSocket, group control ✅ Complete: Basic Svelte frontend with camera grid and group controls 📝 Next Steps: Testing with physical iPhones, advanced features (LOT B+)
- Rust 1.70+ (install from rustup.rs)
- Node.js 18+ and npm
- Operating System: macOS, Windows, or Linux
# Install Rust dependencies
cd tauri-controller
cargo fetch
# Install Node dependencies
npm installRun the app in development mode (with hot-reload):
npm run tauri:devThis will:
- Start the Vite dev server (frontend)
- Compile and run the Rust backend
- Open the application window
Build distributable packages:
npm run tauri:buildThis creates:
- macOS:
.appbundle and.dmginstaller insrc-tauri/target/release/bundle/ - Windows:
.exeinstaller insrc-tauri/target/release/bundle/ - Linux:
.AppImageand.debinsrc-tauri/target/release/bundle/
src-tauri/src/
├── main.rs # Tauri commands and app setup
├── models.rs # Data structures (matches iOS API)
├── camera_discovery.rs # mDNS/Bonjour discovery
├── camera_client.rs # HTTP/WebSocket client
└── camera_manager.rs # Multi-camera coordination + group control
Key Technologies:
- Tauri 2.0: Cross-platform desktop framework
- tokio: Async runtime
- reqwest: HTTP client with 5s timeout
- tokio-tungstenite: WebSocket client with auto-reconnect
- mdns-sd: mDNS/Bonjour discovery (
_avolocam._tcp.local.) - tokio::sync::Semaphore: Bounded concurrency (max 10 parallel operations)
src/
├── main.js # App entry point
├── App.svelte # Main UI component
└── app.css # Global styles
Key Features:
- Real-time camera status (2s polling)
- Grid layout with telemetry cards
- Multi-select checkboxes for group control
- Manual camera addition dialog
- Responsive design
- Ensure iPhones running AvoCam are on the same WiFi network
- Launch the controller
- Cameras should appear automatically via mDNS
- If not appearing: click "+ Add Camera" to add manually
- Click "+ Add Camera"
- Enter:
- IP address (find in iPhone WiFi settings)
- Port (default: 8888)
- Bearer token (displayed in iPhone app console or UI)
- Click "Add"
Single Camera:
- Click "
▶️ Start" to begin streaming - Click "⏹ Stop" to end streaming
- View real-time telemetry (FPS, bitrate, battery, temp)
Group Control:
- Check boxes next to cameras to select
- Click "
▶️ Start All" or "⏹ Stop All" in the Group Control section - Operations run in parallel with bounded concurrency (max 10 simultaneous)
- Click "✕" button in camera card header
- Confirm removal
The Tauri backend exposes these commands to the frontend:
// Discovery
discover_cameras() -> Vec<DiscoveredCamera>
// Camera management
add_camera_manual(ip, port, token) -> String (camera_id)
remove_camera(camera_id) -> ()
get_cameras() -> Vec<CameraInfo>
get_camera_status(camera_id) -> StatusResponse
// Single camera control
start_stream(camera_id, resolution, framerate, bitrate, codec) -> ()
stop_stream(camera_id) -> ()
update_camera_settings(camera_id, settings) -> ()
force_keyframe(camera_id) -> ()
// Group control (returns per-camera results)
group_start_stream(camera_ids, resolution, framerate, bitrate, codec) -> Vec<GroupCommandResult>
group_stop_stream(camera_ids) -> Vec<GroupCommandResult>
group_update_settings(camera_ids, settings) -> Vec<GroupCommandResult>
// Aliases
update_camera_alias(camera_id, alias) -> ()- Same Subnet: Controller and iPhones must be on the same network segment
- Multicast: Network must allow multicast packets (required for mDNS)
- Ports:
- HTTP: 8888 (default, configurable per camera)
- WebSocket: same port as HTTP (
/wsendpoint)
If cameras don't appear automatically:
- Check multicast support: Some networks block multicast (guest networks, VLANs with IGMP snooping)
- Firewall: Ensure firewall allows mDNS (port 5353 UDP)
- Fallback: Use manual camera addition
Test mDNS from terminal:
# macOS/Linux
dns-sd -B _avolocam._tcp.
# Should show discovered camerasChanges to Svelte files trigger instant hot-reload.
Changes to Rust files require recompilation (handled automatically by tauri dev).
Set log level:
RUST_LOG=debug npm run tauri:devLogs show:
- mDNS discovery events
- HTTP requests/responses
- WebSocket connections/disconnections
- Group operation results
-
Add Tauri command in
src-tauri/src/main.rs:#[tauri::command] async fn my_command(state: State<'_, AppState>) -> Result<T, String> { // Implementation }
-
Register command in
invoke_handler!:.invoke_handler(tauri::generate_handler![ // ... existing commands my_command, ])
-
Call from frontend in Svelte:
import { invoke } from '@tauri-apps/api/core'; const result = await invoke('my_command', { arg1, arg2 });
- Rust backend structure
- mDNS camera discovery
- HTTP client with Bearer token auth
- WebSocket client with auto-reconnect (exponential backoff)
- Camera manager with group control
- Bounded concurrency (Semaphore-based)
- Svelte frontend with grid view
- Manual camera addition
- Real-time telemetry display
- Group start/stop controls
- Profile management: Save/recall/copy camera settings
- Resolution/FPS selector in UI
- Camera settings panel: WB, ISO, shutter controls
- Readonly mode: Monitor-only toggle
- Per-camera result chips: Show individual success/failure for group ops
- Telemetry charts: Sparklines for FPS/bitrate/temp history
- Network quality indicators: Visual RSSI strength, warnings
- Thermal warnings: Alert when cameras overheat
- Config backup/restore: Export/import full fleet configuration
- Orientation lock: UI control
- Lens selector: Ultra-wide/wide/tele
- WB presets: 3200K/4300K/5600K quick buttons
- Start mock camera server (or use actual iPhone)
- Run controller:
npm run tauri:dev - Add camera manually
- Verify connection and telemetry
- Deploy AvoCam to ≥3 iPhones
- Connect all to same WiFi
- Launch controller
- Verify automatic discovery
- Test group operations:
- Start all streams
- Check OBS for NDI sources
- Stop all streams
- Group operation latency: Should complete in <250ms for 3 cameras
- Telemetry update rate: 1Hz (2s polling + 1Hz WebSocket)
- Memory usage: Monitor over 2h period with 6 cameras
- WebSocket reconnection: Current implementation uses simple exponential backoff; may need improvement for flaky networks
- Telemetry callback: Currently just logs; needs proper state management for UI updates
- mDNS on Windows: May require additional firewall rules
- Settings profiles (save/recall/copy)
- Network quality indicators
- Error model improvements
- Per-cam rename
- Read-only mode
- Full camera controls (orientation, lens, anti-banding)
- IDR on demand
- Test pattern generation
- Diagnostics endpoint integration
- Log download
- Telemetry charts (sparklines)
- Config backup/restore
- Tauri Documentation
- Svelte Tutorial
- Tokio Async Runtime
- mdns-sd Crate
- reqwest HTTP Client
- LOT A Checklist
- CLAUDE.md
For issues and questions:
- Check logs:
RUST_LOG=debug npm run tauri:dev - Review inline code comments
- See architecture docs in CLAUDE.md