This is a full-stack web application designed to manage and view ONVIF-compliant IP cameras and generic RTSP cameras. It consists of a Node.js backend and a React frontend, supporting multiple camera types with intelligent feature detection.
- Multi-Camera Type Support: Seamlessly manage both ONVIF and RTSP cameras from a single interface.
- ONVIF Cameras: Full feature support including discovery, time sync, and PTZ control
- RTSP Cameras: Generic RTSP streams (IP cameras, MediaMTX, etc.) with streaming and recording capabilities
- Camera Discovery: Automatically discover ONVIF cameras on your local network using subnet scanning with unicast WS-Discovery probes.
- Camera Management: Register, update, delete, and list cameras. Each camera type is clearly identified with visual badges (ONVIF/RTSP).
- Time Synchronization: Synchronize ONVIF camera time with the server's system time. Cameras are automatically synced when registered, and can be manually synced anytime.
- Multi-Camera Live Streaming: View up to 4 live HLS streams simultaneously in a 2Γ2 grid layout. Each camera stream operates independently with its own controls.
- PTZ Control: Control Pan-Tilt-Zoom (PTZ) cameras directly from the web interface with intuitive directional controls and zoom slider. PTZ controls are automatically displayed for ONVIF cameras that support the feature.
- Independent Recording: Record video from multiple cameras simultaneously. Each camera has its own recording controls. Auto-generates thumbnails from recordings. Works with both ONVIF and RTSP cameras.
- Video Playback: Browse recordings in a 4-column grid with thumbnail previews. Play back recorded MP4 files in a modal player. Recordings from deleted cameras remain accessible.
- Connection Testing: Automatically tests the ONVIF connection before saving camera details (ONVIF cameras only).
- Session Persistence: Active camera streams are automatically restored after page reload.
- REST API: Provides a simple API to interact with the camera data and streaming processes.
This application supports two types of cameras with different feature sets:
Full-featured IP cameras using the ONVIF protocol standard.
Supported Features:
- β Live streaming (HLS)
- β Recording (MP4 with auto-generated thumbnails)
- β Network discovery (automatic camera detection)
- β Time synchronization with server
- β PTZ control (for cameras with PTZ support)
- β Connection testing before registration
Use Cases: Professional IP cameras, NVRs, and network cameras that support ONVIF standards.
Generic RTSP streaming sources including IP cameras, MediaMTX servers, and other RTSP-compatible devices.
Supported Features:
- β Live streaming (HLS)
- β Recording (MP4 with auto-generated thumbnails)
- β Network discovery (not available)
- β Time synchronization (not available)
- β PTZ control (not available)
Use Cases:
- Generic IP cameras with RTSP output
- UVC (USB) cameras streamed through MediaMTX or similar RTSP servers
- Custom RTSP sources
- IP cameras without ONVIF support
Note: For detailed information on setting up UVC cameras with MediaMTX for RTSP streaming, please refer to the MediaMTX Setup Guide.
![]() |
![]() |
| Start camera discovery | Scanning network for ONVIF cameras |
List of discovered ONVIF cameras on the network with registration status
Multi-camera live streaming in 2Γ2 grid layout - view up to 4 cameras simultaneously with independent controls for each stream
Pan-Tilt-Zoom controls with directional buttons and zoom slider for PTZ-enabled cameras
Browse, play, and manage recorded video files with timestamp and camera information
- Runtime: Node.js
- Framework: Express.js
- Database: SQLite3 with Knex.js
- ONVIF Protocol: onvif
- Video Processing: FFmpeg for RTSP to HLS transcoding.
- CORS: cors for handling cross-origin requests.
- Framework: React with Vite
- Language: TypeScript
- UI Library: Material-UI (MUI)
- API Client: Axios
- Video Playback: hls.js
The project is divided into two main parts:
/backend: The Node.js/Express server that handles all camera communication and video processing./frontend: The React single-page application that provides the user interface.
- DEPLOYMENT.md - Comprehensive guide for building and deploying the application
- Development workflow and commands
- Production build process
- Local testing with
npm run preview - Deployment instructions for production servers
- Nginx configuration examples
- Troubleshooting common issues
- Node.js (v16 or later recommended)
- npm
- FFmpeg must be installed on the machine running the backend server and available in the system's PATH.
You need to run both the backend and frontend servers in separate terminals for the application to work.
1. Backend Server:
# Navigate to the backend directory
cd backend
# Install dependencies
npm install
# Run database migrations (first time setup)
npx knex migrate:latest
# Run the development server
npm run devThe backend will be running at http://localhost:3001.
The backend uses Knex.js as a SQL query builder and migration tool with SQLite3.
Database Configuration:
- Database file:
backend/src/db/dev.sqlite3(auto-created on first migration) - Configuration file:
backend/knexfile.js - Migrations directory:
backend/src/db/migrations/
Available Migrations:
20251015144534_create_cameras_table.js- Creates thecamerastable20251016140916_add_xaddr_to_cameras.js- Addsxaddrcolumn for custom ONVIF URLs20251018120100_create_recordings_table.js- Creates therecordingstable20251031131841_add_thumbnail_to_recordings.js- Addsthumbnailcolumn for recording preview images20251109000000_add_rtsp_camera_support.js- Adds RTSP camera support (type, stream_path, timestamps)
Common Knex Commands:
cd backend
# Run all pending migrations
npx knex migrate:latest
# Rollback the last batch of migrations
npx knex migrate:rollback
# Check migration status
npx knex migrate:status
# Create a new migration file
npx knex migrate:make migration_nameDatabase Schema:
cameras table:
id(primary key, auto-increment)name(text) - Camera display nametype(text) - Camera type: 'onvif' or 'rtsp' (default: 'onvif')host(text) - IP address or hostnameport(integer) - Port number (ONVIF: typically 80, RTSP: server-dependent)user(text, nullable) - Authentication usernamepass(text, nullable) - Authentication passwordxaddr(text, nullable) - Custom ONVIF device service URL (ONVIF only)stream_path(text, nullable) - RTSP stream path (RTSP cameras only, e.g., '/uvc_camera_1')created_at(datetime) - Creation timestampupdated_at(datetime) - Last update timestamp
recordings table:
id(primary key, auto-increment)camera_id(integer, foreign key) - Reference to cameras tablefilename(text) - MP4 filenamethumbnail(text, nullable) - Thumbnail image filename (JPG)start_time(datetime) - Recording start timestampend_time(datetime, nullable) - Recording end timestampis_finished(boolean, default: false) - Recording completion status
2. Frontend Server:
# From the project root, navigate to the frontend directory
cd frontend
# Install dependencies
npm install
# Run the development server
npm run dev
# Build for production
npm run build
# Preview production build
npm run previewThe frontend development server will be running at http://localhost:5173 and should open automatically in your browser.
Frontend Build Commands:
npm run dev- Start Vite development server with hot module replacement (HMR)npm run build- Build for production (runs TypeScript compiler + Vite build). Output goes todist/directorynpm run lint- Run ESLint to check code qualitynpm run preview- Preview the production build locally
Once the application is running, you can manage your cameras through the web interface.
- Discovering Cameras (ONVIF only): Click the "Discover Cameras" button to automatically scan your local network. The scan will probe each IP address in your subnet and may take 2-3 minutes.
- Discovered cameras that are already registered will be marked as "Registered".
- You can add unregistered cameras by providing their credentials. The discovery window will remain open, allowing you to add multiple cameras without re-scanning.
- Adding a Camera Manually: Click the "Add Camera" button to open a dialog where you can choose the camera type:
- ONVIF Camera: Provide Name, Host/IP, Port, Username, Password (optional: xaddr). The system tests the ONVIF connection before adding. After successful registration, the camera's time is automatically synchronized with the server.
- RTSP Camera: Provide Name, RTSP Server Host, Port, Stream Path, and optional credentials. This option supports generic RTSP sources including IP cameras, MediaMTX servers streaming from UVC cameras, and other RTSP-compatible devices. No connection test is performed - the stream will be validated when you start viewing it.
- Synchronizing Camera Time (ONVIF only): Click the sync icon (β³) next to any ONVIF camera in the list to manually synchronize its time with the server's system time. A notification will confirm success or display any errors. This feature is not available for RTSP cameras.
- Deleting a Camera: Click the red delete icon (ποΈ) next to a camera in the main list. A confirmation prompt will appear before deletion.
- Viewing Multiple Streams:
- Click the "View Stream" button next to a camera to add it to the grid view.
- You can view up to 4 cameras simultaneously in a 2Γ2 grid layout.
- Each camera stream has its own controls and operates independently.
- Click "Stop Stream" in the camera list or the "Close" button in the grid to remove a camera from view.
- If you try to add a 5th camera, you'll receive an alert indicating the maximum limit has been reached.
- Active streams are saved in session storage and will be automatically restored when you refresh the page.
- PTZ Control: For cameras that support PTZ (Pan-Tilt-Zoom), a control panel will automatically appear below the video player for each camera.
- Use the directional arrow buttons (β β β β) to pan and tilt the camera. Press and hold to move; release to stop.
- Use the zoom slider or +/- buttons to zoom in and out. The camera will automatically stop zooming when you release the control.
- All PTZ controls support both mouse and touch input for mobile devices.
- Each camera's PTZ controls operate independently.
- Recording: Each camera stream has its own "Start Recording" and "Stop Recording" buttons.
- You can record from multiple cameras simultaneously.
- The recording status (REC indicator) is displayed for each camera independently.
- Recordings are saved as MP4 files on the server.
- When a recording is stopped, a thumbnail is automatically generated from the video (captured at the 2-second mark).
- New recordings appear immediately in the recordings list after stopping (no page reload required).
- Playback & Management: Completed recordings are displayed in a 4-column grid layout with thumbnail previews.
- Each recording card shows: thumbnail image, camera name, filename, and start/end timestamps.
- Click the "Play" button to watch a recording in a modal player.
- Recordings from deleted cameras will be labeled accordingly and remain playable.
- Click the red delete icon (ποΈ) to permanently delete a recording. A confirmation prompt will appear before deletion. This will remove both the database record, the MP4 file, and the thumbnail from the server.
The backend provides the following REST API endpoints for programmatic access or debugging.
Retrieves a list of all registered cameras.
Discovers ONVIF cameras on the local network using subnet scanning. This endpoint performs unicast WS-Discovery probes to each IP address in the subnet (default: 192.168.0.1-254). The scan typically takes 2-3 minutes to complete.
Query Parameters (optional):
subnet: Subnet base address (e.g.,192.168.1)start: Starting IP address (e.g.,1)end: Ending IP address (e.g.,254)
Response: Returns an array of discovered devices with their IP addresses, ports, device names, and ONVIF service URLs.
Registers a new camera. For ONVIF cameras, it tests the connection before saving. For RTSP cameras, the stream is validated when viewing starts.
Request Body for ONVIF Camera:
{
"name": "Front Door Camera",
"type": "onvif",
"host": "192.168.1.100",
"port": 80,
"user": "admin",
"pass": "password",
"xaddr": "http://192.168.1.100:80/onvif/device_service" // optional
}Request Body for RTSP Camera:
{
"name": "USB Camera via MediaMTX",
"type": "rtsp",
"host": "localhost",
"port": 8554,
"stream_path": "/uvc_camera_1",
"user": "username", // optional
"pass": "password" // optional
}Updates an existing camera's information. Useful for adding or correcting details like the xaddr.
Example Body: { "xaddr": "http://192.168.1.100:8080/onvif/device_service" }
Deletes a registered camera from the database.
Starts the FFmpeg process to convert the camera's RTSP stream to HLS. Returns the relative URL of the HLS playlist (e.g., /streams/1/index.m3u8).
Stops the FFmpeg process for the specified camera.
Starts a new recording for the specified camera. The video is saved as an MP4 file on the server.
Stops an in-progress recording and finalizes the MP4 file.
Retrieves the current date and time from the specified camera via ONVIF, along with the server's current time for comparison.
Response Example:
{
"cameraTime": { /* ONVIF date/time object */ },
"serverTime": "2025-01-24T12:00:00.000Z"
}Synchronizes the specified camera's system time with the server's current time using ONVIF's SetSystemDateAndTime method. The time is set in UTC format.
Response Example:
{
"success": true,
"beforeTime": { /* ONVIF date/time object before sync */ },
"serverTime": "2025-01-24T12:00:00.000Z",
"message": "Camera time synchronized successfully"
}Note: The camera's time is synchronized to the server's system time. Ensure the server has accurate time (e.g., via NTP) for proper synchronization.
Checks if the specified camera supports PTZ (Pan-Tilt-Zoom) functionality.
Response Example:
{
"supported": true,
"capabilities": {
"hasPanTilt": true,
"hasZoom": true
}
}If PTZ is not supported, supported will be false and capabilities will be null.
Moves the camera using continuous PTZ movement. The camera will continue moving in the specified direction until a stop command is sent.
Request Body:
{
"x": 0.5, // Pan speed: -1.0 (left) to 1.0 (right), 0 = no movement
"y": 0.3, // Tilt speed: -1.0 (down) to 1.0 (up), 0 = no movement
"zoom": 0.2, // Zoom speed: -1.0 (out) to 1.0 (in), 0 = no movement
"timeout": 1000 // Optional: auto-stop after N milliseconds
}Response:
{
"success": true,
"message": "Camera movement started"
}Note: Values represent movement speed/direction. Use 0.5 for moderate speed, 1.0 for maximum speed.
Stops all ongoing PTZ movements (pan, tilt, and zoom).
Request Body (optional):
{
"panTilt": true, // Stop pan/tilt movement (default: true)
"zoom": true // Stop zoom movement (default: true)
}Response:
{
"success": true,
"message": "Camera movement stopped"
}Retrieves a list of all completed recordings, including camera name and file details. Recordings from deleted cameras are included.
Deletes a recording by its ID. This removes both the database record and the associated MP4 file from the server's filesystem.
Response: Returns 204 No Content on success.
Error Handling:
- If the recording ID is not found, returns
404 Not Found - If the file cannot be deleted but exists in the database, the database record is still removed to prevent orphaned records


