Stack: React • Node.js • Mediasoup • Redis • MongoDB • Tauri AGENT
This document walks you through end‑to‑end setup for remote PTZ (pan/tilt/zoom) control of UVC cameras, piggy‑backing on your existing Mediasoup/WebRTC pipeline and Tauri agent apps (streaming system audio + screen without confirmation popups).
- Overview
- Prerequisites
- Development & Runtime Environments
- Installed Software & Libraries
- Architecture Diagram
- Signaling & State Persistence
- Frontend (React) Implementation
- Mediasoup Device & SendTransport
- Creating PTZ DataProducer
- UI Components & Event Debouncing
- Agent (Tauri + Node) Implementation
- Tauri App Overview & Setup
- Embedding Mediasoup‑Client (wrtc) in Tauri
- Consuming PTZ DataChannel
- Invoking UVCControl for PTZ
- Server (Node.js) & Mediasoup Router
- Redis‑Backed Signaling
- MongoDB for Session Metadata
- Exchanging Routers & Transports
- Security & Deployment
- Authentication & Authorization
- TLS & Reverse Proxy
- Scaling Agents
- Troubleshooting & Tips
- References
We extend your existing Mediasoup WebRTC flow by adding a reliable SCTP DataChannel named “ptz”. The browser sends JSON commands ({ control, value }) to the Tauri agent, which applies them via uvc-control. No extra HTTP ports are required.
- Node.js v14+
- npm/yarn
- Rust & Cargo (for Tauri)
- Visual Studio Build Tools (Windows) or
build-essential+libusb-1.0-0-dev(Linux) foruvc-control - Mediasoup (server & client)
- Redis (pub/sub for signaling)
- MongoDB (optional: session metadata)
# Server
npm install mediasoup express socket.io redis mongoose
# React Client
npm install mediasoup-client socket.io-client
# Tauri Agent
npm install mediasoup-client wrtc uvc-control
cargo install tauri-cli[Browser (React+mediasoup-client)]
│
│ SCTP DataChannel "ptz" (JSON commands)
▼
[Mediasoup Server Router] ←→ [Redis Pub/Sub] ←→ [Signaling Node.js]
▲ ▲
│ │
[Agent (Tauri App: wrtc+uvc-control)]──────────┘
(Stream video + DataConsumer; System audio + screen)
- Redis Pub/Sub channels:
signal:join,signal:offer,signal:answer,signal:icesignal:ptzfor DataProducer IDs & SCTP parameters
- MongoDB collections:
sessions(userID, agentID, routerId, transportIds)ptzLogs(timestamp, agentID, control, value)
import { Device } from 'mediasoup-client';
import io from 'socket.io-client';
const socket = io(SIGNALING_URL);
const device = new Device();
// 1) Load router capabilities
socket.on('router:rtpCapabilities', async caps => {
await device.load({ routerRtpCapabilities: caps });
// 2) Create video sendTransport
const transportInfo = await socketRequest('createSendTransport');
sendTransport = device.createSendTransport(transportInfo);
// Signal transport events...
});// After sendTransport ready:
const ptzProducer = sendTransport.produceData({
ordered: true,
maxPacketLifeTime: 3000,
label: 'ptz',
protocol: '',
appData: { sessionId }
});
// Signal PTZ producer details
socket.emit('ptz:produce', {
producerId: ptzProducer.id,
sctpStreamParameters: ptzProducer.sctpStreamParameters
});// Pan buttons
<button onClick={() => ptzProducer.send(JSON.stringify({ control:'absolute_pan', value: -30 }))}>⟵ Pan</button>
// Zoom slider with debounce
import { useCallback } from 'react';
import debounce from 'lodash.debounce';
const sendZoom = useCallback(debounce(value => {
ptzProducer.send(JSON.stringify({ control:'zoom_absolute', value }));
}, 100), [ptzProducer]);
<input
type="range"
min={0} max={500}
onChange={e => sendZoom(+e.target.value)}
/>- Initialize Tauri:
cargo tauri init
- Include Node modules:
Adduvc-control,mediasoup-client,wrtcto your Tauri frontend or bundled Node side.
// preload.js or main.rs invoke
import { Device } from 'mediasoup-client';
import wrtc from 'wrtc';
const device = new Device({ wrtc });
await device.load({ routerRtpCapabilities });
// Create recvTransport for media & data
const recvInfo = await invoke('get_recv_transport_params', { sessionId });
const recvTransport = device.createRecvTransport(recvInfo);
// Signal transport events back...// After receiving ptzProducerId & SCTP params
const ptzConsumer = await recvTransport.consumeData({
id: ptzId,
sctpStreamParameters,
label: 'ptz'
});
ptzConsumer.on('message', raw => {
const { control, value } = JSON.parse(raw);
uvcCam.set(control, value)
.catch(console.error);
});import UVCControl from 'uvc-control';
const uvcCam = new UVCControl();
// Use device path or index if needed:
// const uvcCam = new UVCControl('/dev/video1');// on client connect:
socket.on('ptz:produce', ({ producerId, sctpStreamParameters }) => {
redisClient.hset(`session:${sessionId}`, 'ptzProdId', producerId);
redisClient.hset(`session:${sessionId}`, 'ptzParams', JSON.stringify(sctpStreamParameters));
// Notify agent via pub/sub:
redisClient.publish(`agent:${agentId}:ptz`, JSON.stringify({ producerId, sctpStreamParameters }));
});const Session = mongoose.model('Session', new Schema({
sessionId: String,
userId: String,
agentId: String,
routerId: String,
sendTransportId: String,
recvTransportId: String,
ptzProdId: String,
ptzParams: Schema.Types.Mixed
}));- Standard mediasoup signaling:
createTransport,connectTransport,produce,consume.
- JWT or OAuth for user → central server.
- Mutual TLS or token for server → agent channel (via Redis pub/sub).
- Terminate HTTPS with Nginx or Caddy for both web UI and agent tunnel.
- Run agents on each physical/VM host.
- Use Kubernetes DaemonSet if containerized.
- Firewall/NAT: ensure WebRTC ICE candidates traverse.
- Multiple cameras: pass device path to
new UVCControl(path). - Logging: emit events to Redis streams for audit.
- Debounce commands to avoid PTZ jitter.