AI assistant context for StreamGRID. Read this before touching any code.
StreamGRID is a cross-platform Electron desktop application for monitoring multiple live video streams simultaneously in configurable grid layouts. It plays HLS and HTTP streams (native RTMP cannot play in-browser and requires HLS transcoding). The app targets professional streaming workflows: broadcast monitoring, content production, security surveillance.
Version: 1.0.0
AppId: com.streamgrid.app
Repo: https://github.com/sanchez314c/stream-grid
| Layer | Technology |
|---|---|
| Shell | Electron 39 |
| UI | React 18 + TypeScript 5 |
| Bundler | Vite 7 (dev server port: 52774) |
| Styling | Tailwind CSS 3 + custom CSS tokens |
| State | Zustand 5 + Immer 10 |
| Server state | TanStack React Query 5 |
| Video | HLS.js 1.6 |
| Database | sqlite3 5 (userData/streams.db) |
| Settings | JSON file (userData/settings.json) — NOT electron-store |
| Logging | Winston 3 |
| Tests | Vitest 3 + React Testing Library |
| Packager | electron-builder 26 |
src/main/ — Node.js main process (full fs/system access)
src/preload/ — Context bridge (index.ts exposes window.api)
src/renderer/ — React renderer (sandboxed, no Node access)
src/shared/ — Types shared across processes
CRITICAL: Never set experimentalFeatures: true in webPreferences — it breaks contextBridge IPC on Linux.
| File | Role |
|---|---|
src/main/index.ts |
App entry. Sets GPU flags before app.ready, calls initializeApp() |
src/main/window.ts |
Creates BrowserWindow (960x960, frameless, transparent). Sets app menu, IPC handlers for window controls. Validates external URLs to http/https/mailto only. |
src/main/database/database.ts |
SQLite CRUD for streams and layouts tables |
src/main/settings.ts |
Reads/writes settings.json. updateSettings() validates path against ALLOWED_SETTING_PATHS whitelist |
src/main/ipc/handlers.ts |
Registers all IPC channels: stream:*, layout:*, settings:*, system:*, monitoring:*, hardware:*, discovery:* |
src/main/ipc/advanced-controls-handlers.ts |
Advanced controls IPC: recording, VFX, randomizer, stream control |
src/main/ipc/stream-validator.ts |
validateStreamUrl(), parseStreamUrl(), normalizeStreamUrl() |
src/main/discovery/discovery-service.ts |
DiscoveryService singleton. Orchestrates NetworkScanner sessions. Emits discovery:progress, discovery:completed, discovery:error |
src/main/discovery/network-scanner.ts |
Port scanning for RTSP/HTTP/ONVIF/MJPEG |
src/main/monitoring/hardware-verifier.ts |
HardwareVerifier singleton. Platform-specific: nvidia-smi (Linux NVIDIA), VAAPI, VideoToolbox (macOS), DXVA (Windows) |
src/main/monitoring/bandwidth-monitor.ts |
EventEmitter, emits metrics events forwarded to renderer |
src/main/monitoring/gpu-monitor.ts |
GPU metrics, forwarded to renderer |
src/preload/index.ts |
Exposes window.api (stream, layout, settings, discovery, monitoring, hardware, advancedControls, storage) and window.electronAPI (window controls) |
src/renderer/App.tsx |
Root component. Loads streams/layouts via React Query. Registers menu IPC listeners. |
src/renderer/store/index.ts |
useStreamGridStore — Zustand+Immer+persist. Serializes Map/Set to/from JSON for localStorage. |
src/renderer/components/StreamGrid.tsx |
Grid layout using PRESET_LAYOUTS. Handles pagination with streamPageOffset. Auto-cycles pages every 60s when streams > slots. |
src/renderer/components/StreamTile.tsx |
Per-stream tile with overlay controls |
src/renderer/components/VideoPlayer.tsx |
HLS.js or native video. GPU/CPU decode path controlled by accelerationMode in store. Configurable buffer (1-30s via bufferSize). |
src/shared/types/layout.ts |
PRESET_LAYOUTS: 1x1, 2x1, 3x1, 2x2, 3x3, 4x4 |
src/shared/types/settings.ts |
AppSettings, DEFAULT_SETTINGS, advanced controls profiles |
# Development (starts Vite + Electron together)
npm run dev
# Run from source (Linux) — use this for iteration
./run-source-linux.sh
# Type check
npm run type-check
# Lint
npm run lint
# Tests
npm test
# Production build
npm run build # tsc + vite build
npm run dist:linux # AppImage + deb + rpm + snap
npm run dist:mac # DMG (x64 + arm64)
npm run dist:win # NSIS + MSI + ZIPAll channels use ipcMain.handle / ipcRenderer.invoke pattern.
Stream: stream:add, stream:update, stream:delete, stream:getAll, stream:get, stream:save, stream:validate
Layout: layout:create, layout:update, layout:delete, layout:getAll, layout:setActive
Settings: settings:get, settings:update
System: system:getInfo
Monitoring: monitoring:startBandwidth, monitoring:stopBandwidth, monitoring:getBandwidthMetrics, monitoring:startGPU, monitoring:stopGPU, monitoring:getGPUInfo, monitoring:getHardwareVerification, monitoring:performHardwareVerification
Hardware: hardware:verify, hardware:getVerification, hardware:getVideoToolboxInfo, hardware:startContinuousVerification, hardware:stopContinuousVerification
Discovery: discovery:start, discovery:stop, discovery:getProgress, discovery:getResults
Advanced Controls: advanced-controls:start-recording, advanced-controls:stop-recording, advanced-controls:pause-recording, advanced-controls:get-recording-state, advanced-controls:randomize-now, advanced-controls:randomize-single-tile, advanced-controls:apply-vfx-filter, advanced-controls:update-vfx-intensity, advanced-controls:reload-streams, advanced-controls:pause-streams, advanced-controls:stop-streams
Window: minimize-window, maximize-window, close-window, open-external
Storage: storage:getInfo, storage:openRecordingFolder, storage:cleanup
Settings are updated via settings:update IPC with a dot-path string. Only paths in ALLOWED_SETTING_PATHS are accepted. Key paths:
general.theme,general.minimizeToTray,general.defaultLayoutdisplay.alwaysOnTop,display.showLabels,display.showStatisticsperformance.maxConcurrentStreams,performance.gpuDecoding,performance.accelerationModeaudio.defaultVolume,audio.defaultMutedadvanced.logLevel,advanced.rtmpTimeout
streams table: id TEXT PK, url, label, status, metadata JSON, settings JSON, statistics JSON, createdAt, updatedAt
layouts table: id TEXT PK, name, type, grid JSON, streams JSON, isActive INTEGER, createdAt, updatedAt
Database location: app.getPath('userData')/streams.db
- RTMP URLs cannot play directly.
VideoPlayer.tsxsurfaces an error and tells the user to use HLS (.m3u8). RTMP validation still works for storage. sqlite3is a native module. After any Electron version upgrade, runnpm run rebuild.- Dev server port is 52774 (hardcoded in
vite.config.tsandsrc/main/window.ts). experimentalFeatures: truein BrowserWindow webPreferences breaks contextBridge IPC on Linux. Never set it.- Zustand store uses custom serialization in the persist middleware to handle Map/Set (which JSON doesn't support natively). See
storage.getItem/storage.setIteminsrc/renderer/store/index.ts. - Linux requires
--no-sandboxorsudo sysctl -w kernel.unprivileged_userns_clone=1for Electron. - Hardware verification on Linux checks
nvidia-smifirst, thenvainfo, thenradeontop. The status bar shows "GPU MODE" / "CPU MODE" based on storeaccelerationMode. settings.jsonis written directly withfs.writeFileSync— not electron-store (removed due to ESM incompatibility).
| Profile | Keyboard | Recording | VFX | Randomizer |
|---|---|---|---|---|
clean |
off | off | off | off |
broadcast |
on | on | off | off |
creative |
on | on | on | on |
monitoring |
on | off | off | on |