Skip to content

Instantly share code, notes, and snippets.

@j4gd33p
Created June 10, 2025 05:09
Show Gist options
  • Select an option

  • Save j4gd33p/78deaf96a6507cd59af6281c2a219f98 to your computer and use it in GitHub Desktop.

Select an option

Save j4gd33p/78deaf96a6507cd59af6281c2a219f98 to your computer and use it in GitHub Desktop.

Remote PTZ Control Guide

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).


Table of Contents

  1. Overview
  2. Prerequisites
    • Development & Runtime Environments
    • Installed Software & Libraries
  3. Architecture Diagram
  4. Signaling & State Persistence
  5. Frontend (React) Implementation
    • Mediasoup Device & SendTransport
    • Creating PTZ DataProducer
    • UI Components & Event Debouncing
  6. Agent (Tauri + Node) Implementation
    • Tauri App Overview & Setup
    • Embedding Mediasoup‑Client (wrtc) in Tauri
    • Consuming PTZ DataChannel
    • Invoking UVCControl for PTZ
  7. Server (Node.js) & Mediasoup Router
    • Redis‑Backed Signaling
    • MongoDB for Session Metadata
    • Exchanging Routers & Transports
  8. Security & Deployment
    • Authentication & Authorization
    • TLS & Reverse Proxy
    • Scaling Agents
  9. Troubleshooting & Tips
  10. References

Overview

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.

Prerequisites

Development & Runtime Environments

  • Node.js v14+
  • npm/yarn
  • Rust & Cargo (for Tauri)
  • Visual Studio Build Tools (Windows) or build-essential + libusb-1.0-0-dev (Linux) for uvc-control
  • Mediasoup (server & client)
  • Redis (pub/sub for signaling)
  • MongoDB (optional: session metadata)

Installed Software & Libraries

# 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

Architecture Diagram

[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)

Signaling & State Persistence

  1. Redis Pub/Sub channels:
    • signal:join, signal:offer, signal:answer, signal:ice
    • signal:ptz for DataProducer IDs & SCTP parameters
  2. MongoDB collections:
    • sessions (userID, agentID, routerId, transportIds)
    • ptzLogs (timestamp, agentID, control, value)

Frontend (React) Implementation

Mediasoup Device & SendTransport

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...
});

Creating PTZ DataProducer

// 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
});

UI Components & Event Debouncing

// 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)}
/>

Agent (Tauri + Node) Implementation

Tauri App Overview & Setup

  1. Initialize Tauri:
    cargo tauri init
  2. Include Node modules:
    Add uvc-control, mediasoup-client, wrtc to your Tauri frontend or bundled Node side.

Embedding Mediasoup‑Client in Tauri

// 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...

Consuming PTZ DataChannel

// 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);
});

Invoking UVCControl for PTZ

import UVCControl from 'uvc-control';
const uvcCam = new UVCControl();
// Use device path or index if needed:
// const uvcCam = new UVCControl('/dev/video1');

Server (Node.js) & Mediasoup Router

Redis‑Backed Signaling

// 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 }));
});

MongoDB for Session Metadata

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
}));

Exchanging Routers & Transports

  • Standard mediasoup signaling: createTransport, connectTransport, produce, consume.

Security & Deployment

Authentication & Authorization

  • JWT or OAuth for user → central server.
  • Mutual TLS or token for server → agent channel (via Redis pub/sub).

TLS & Reverse Proxy

  • Terminate HTTPS with Nginx or Caddy for both web UI and agent tunnel.

Scaling Agents

  • Run agents on each physical/VM host.
  • Use Kubernetes DaemonSet if containerized.

Troubleshooting & Tips

  • 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.

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment