Running Hikvision's IoT Frontend: Video Streams, 50K Devices, and 24/7 Stability
Foreword
Hikvision, as a globally leading provider of security and IoT solutions, covers businesses across video surveillance, intelligent transportation, access control and alarms, environmental monitoring, and more. In Hikvision's IoT ecosystem, the web frontend is not just a "presentation layer" — it undertakes core business functions such as device management, real-time video preview, massive data visualization, and alarm handling workflows.
This article, starting from actual business scenarios, discusses the technical challenges and engineering practices encountered in the frontend development of Hikvision's IoT platform. It covers video stream playback, large-scale device state management, real-time data visualization, and performance optimization, hoping to provide a reference for frontend colleagues working in IoT / security / industrial internet.
1. Core Challenges of the IoT Platform Frontend
Unlike ordinary web applications, the IoT platform frontend has several distinctive characteristics:
| Feature | Ordinary Web App | IoT Platform Frontend |
|---|---|---|
| Data Update Frequency | Driven by user operations | Second-level or even millisecond-level push |
| Concurrent Device Count | No concept | Thousands to hundreds of thousands |
| Real-time Requirements | Acceptable second-level delay | Video stream <1s delay, alarms <500ms |
| Data Types | Mainly structured text | Video, time-series data, map coordinates, images |
| Reliability Requirements | Refresh on error | 7×24 uninterrupted operation, must not crash |
These characteristics determine that the IoT frontend's architectural design needs to pay special attention to three major issues: real-time communication, massive data rendering, and long-running stability.
2. Real-time Video Stream Playback in the Browser
2.1 Business Scenario
Hikvision's core capability is video surveillance. On the web side, users need to:
- Preview camera feeds in real time (latency requirement < 1s)
- Multi-screen split playback simultaneously (4-split, 9-split, 16-split)
- Play back recordings, supporting variable speed and timeline dragging
- Overlay AI analysis results on the video (person bounding boxes, license plates, behavior annotations)
2.2 Technical Solution Evolution
Early solution: Browser plugin / ActiveX (IE Only)
↓
Transitional solution: Flash Player + RTMP stream
↓
Current solution: WebRTC / HLS / WebAssembly software decoding
WebRTC solution is currently the first choice for low-latency scenarios:
class WebRTCPlayer {
constructor(container) {
this.pc = new RTCPeerConnection();
this.video = document.createElement('video');
this.video.autoplay = true;
this.video.muted = true; // Autoplay policy requires muted
container.appendChild(this.video);
// Receive media stream pushed by the server
this.pc.ontrack = (event) => {
this.video.srcObject = event.streams[0];
};
}
async play(streamUrl) {
// Exchange SDP via signaling server
const offer = await this.pc.createOffer();
await this.pc.setLocalDescription(offer);
const response = await fetch(streamUrl + '/play', {
method: 'POST',
body: JSON.stringify({ sdp: offer.sdp, type: 'offer' }),
});
const answer = await response.json();
await this.pc.setRemoteDescription(answer);
}
destroy() {
this.pc.close();
this.video.srcObject = null;
this.video.remove();
}
}
HLS solution is suitable for playback and scenarios that do not require ultra-low latency. Using hls.js, HLS streams can be played in non-Safari browsers:
import Hls from 'hls.js';
function playHLS(videoEl, streamUrl) {
if (Hls.isSupported()) {
const hls = new Hls({
// In IoT scenarios, custom segment fetching strategies are often needed
maxBufferLength: 10,
maxMaxBufferLength: 30,
liveSyncDuration: 3,
});
hls.loadSource(streamUrl);
hls.attachMedia(videoEl);
return hls;
} else if (videoEl.canPlayType('application/vnd.apple.mpegurl')) {
// Safari native support
videoEl.src = streamUrl;
}
}
2.3 Pitfalls of Multi-Split Screen Management
When 16 split screens play simultaneously, browser resource consumption is extremely high. In practice, the following points need attention:
- WebRTC connection limit: Browsers have an upper limit on the number of PeerConnections held simultaneously. Exceeding this limit causes new connections to fail. The solution is to reuse multiple tracks of the same PeerConnection, or adopt a "software decoding + Canvas rendering" approach.
- GPU memory: Each
<video>element consumes video memory. 16 channels of 1080p video require approximately 1.5GB of video memory. Low-end devices are prone to crashes. - Pause when not visible: When switching to other pages within the split screen, promptly close invisible video streams to release resources.
// Use IntersectionObserver to manage video playback
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const player = entry.target.__player;
if (entry.isIntersecting) {
player.resume();
} else {
player.pause(); // Not in viewport, pause decoding
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.video-cell').forEach((el) => {
observer.observe(el);
});
3. Large-Scale Device State Management
3.1 Business Scenario
A medium-sized IoT project may have 5,000 to 50,000 devices, each with attributes such as online status, alarm status, temperature, and battery level. The frontend needs to:
- Display device lists and statuses in real time
- Support filtering, searching, and grouping
- Instantly update the UI when status changes
3.2 WebSocket Data Pipeline
Device → MQTT Broker → Backend Service → WebSocket → Frontend State Management → UI Rendering
Core problem: There may be hundreds of status change messages per second. How to avoid freezing the UI?
// Message buffering + batch update strategy
class DeviceStatusManager {
constructor() {
this.statusMap = new Map(); // Device status storage
this.pendingUpdates = new Map(); // Changes pending batch update
this.flushTimer = null;
}
// WebSocket message callback
onMessage(data) {
const { deviceId, status, timestamp } = data;
const old = this.statusMap.get(deviceId);
// Only record truly changed data
if (old && old.status === status) return;
this.statusMap.set(deviceId, { status, timestamp });
this.pendingUpdates.set(deviceId, { status, timestamp });
// First message triggers immediately, subsequent ones merge into the next frame
if (!this.flushTimer) {
this.flushTimer = requestAnimationFrame(() => this.flush());
}
}
flush() {
if (this.pendingUpdates.size === 0) {
this.flushTimer = null;
return;
}
const updates = Array.from(this.pendingUpdates.entries());
this.pendingUpdates.clear();
this.flushTimer = null;
// Notify UI update once, avoiding frequent re-renders
this.onBatchUpdate?.(updates);
}
}
3.3 Rendering Thousands of Devices with Virtual Lists
Device lists often contain tens of thousands of entries, making virtual lists essential. vue-virtual-scroller or react-window both work, but IoT scenarios have a special requirement: when list item statuses change in real time, the entire list must not re-render.
// Vue3 example: Split device status into independent reactive units
import { shallowReactive, triggerRef } from 'vue';
const deviceStates = shallowReactive(new Map());
function updateDeviceStatus(deviceId, status) {
deviceStates.set(deviceId, status);
triggerRef(deviceStates); // Manual trigger, no deep proxy
}
// List item component subscribes on demand
const DeviceItem = {
props: ['deviceId'],
setup(props) {
const status = computed(() => deviceStates.get(props.deviceId));
return () => h('div', { class: `device-${status.value}` }, /* ... */);
},
};
The key idea is: treat the Map as a shallow reactive container, with each list item only reading the key it cares about. This way, a single status update only triggers the re-render of the corresponding list item, not the entire list.
4. Real-time Data Visualization
4.1 Time-Series Data Charts
IoT sensor data (temperature, humidity, PM2.5, etc.) is typical time-series data, requiring real-time scrolling line charts.
import * as echarts from 'echarts';
class RealtimeChart {
constructor(container) {
this.chart = echarts.init(container);
this.maxPoints = 300; // Keep the most recent 300 data points
this.xData = [];
this.series = {};
this.chart.setOption({
animation: false, // Disable animation, not needed for real-time data
legend: { top: 8 },
grid: { left: 50, right: 20, top: 40, bottom: 30 },
xAxis: { type: 'category', data: this.xData },
yAxis: { type: 'value' },
series: [],
});
}
pushData(timestamp, values) {
this.xData.push(timestamp);
if (this.xData.length > this.maxPoints) this.xData.shift();
for (const [key, value] of Object.entries(values)) {
if (!this.series[key]) this.series[key] = [];
this.series[key].push(value);
if (this.series[key].length > this.maxPoints) this.series[key].shift();
}
// Incremental update, not full setOption
this.chart.setOption({
xAxis: { data: this.xData },
series: Object.entries(this.series).map(([name, data]) => ({
name, type: 'line', data, showSymbol: false,
})),
});
}
}
Performance points:
- Disable animation:
animation: false. Animation on real-time data makes the chart "float". - Limit the number of data points: The frontend only keeps the most recent N points; historical data is queried from the server.
- Incremental update: Use
setOptionmerge mode instead of full replacement. - Downsampling: When data points exceed 1000, thin out old data.
4.2 Device Map Distribution
Hikvision's devices often have geographic location information and need to be displayed on a map. When device density is high (several hundred cameras in a campus), directly rendering markers will cause lag.
// AMap example: Use cluster points + Canvas layer
const cluster = new AMap.MarkerCluster(map, [], {
gridSize: 60,
maxZoom: 18,
renderClusterMarker: (context) => {
const count = context.count;
const size = Math.min(24 + count / 5, 48);
context.marker.setContent(
`<div class="cluster-marker" style="width:${size}px;height:${size}px">
<span>${count}</span>
</div>`
);
},
});
// When device status changes, only update the corresponding marker
function updateMarker(deviceId, status) {
const marker = markerMap.get(deviceId);
if (!marker) return;
// Only update the style class, do not rebuild the DOM
marker.getContent().className = `device-marker status-${status}`;
}
5. Stability for Long-Running Operations
IoT large screens typically run 7×24 hours uninterrupted. Memory leaks are the number one killer.
5.1 Common Leak Points
| Leak Source | Cause | Solution |
|---|---|---|
| WebSocket not closed | Not cleaned up on page switch / component destruction | Close in onUnmounted |
| ECharts instance not destroyed | Repeated init on the same container | dispose() + reuse instance |
| setInterval remnants | Timer not cleared | Register uniformly in a cleanup list |
| Closure referencing DOM | Event listener holds removed DOM | Manage with AbortController |
| Video srcObject | Not set to null after closing video | Explicit cleanup |
5.2 Unified Resource Management
// Resource registrar: Automatically cleans up all registered resources on component destruction
function useResourceManager() {
const cleaners = [];
const timers = [];
const controllers = [];
const api = {
register(cleaner) { cleaners.push(cleaner); },
setInterval(fn, delay) {
const id = setInterval(fn, delay);
timers.push(id);
return id;
},
createAbortController() {
const ctrl = new AbortController();
controllers.push(ctrl);
return ctrl;
},
cleanup() {
cleaners.forEach((fn) => { try { fn(); } catch {} });
timers.forEach(clearInterval);
controllers.forEach((c) => c.abort());
cleaners.length = 0;
timers.length = 0;
controllers.length = 0;
},
};
// Auto-bind in Vue3
if (getCurrentInstance()) {
onUnmounted(api.cleanup);
}
return api;
}
5.3 Memory Monitoring
Add memory monitoring to the large screen page, auto-refresh when a threshold is exceeded:
if (performance.memory) {
setInterval(() => {
const { usedJSHeapSize, jsHeapSizeLimit } = performance.memory;
const usage = usedJSHeapSize / jsHeapSizeLimit;
if (usage > 0.85) {
console.warn(`Memory usage ${(usage * 100).toFixed(1)}%, about to auto-refresh`);
// Give the user a prompt, then refresh
ElNotification({
title: 'System Prompt',
message: 'Running for too long, auto-refreshing to optimize performance',
type: 'warning',
duration: 3000,
onClose: () => location.reload(),
});
}
}, 60000);
}
6. Architecture Practice: Micro-Frontend Splitting
Hikvision's IoT platform typically includes multiple subsystems such as video surveillance, access control management, alarm center, and data analysis. As the monolithic application becomes increasingly bloated, micro-frontends are a natural choice.
6.1 Splitting Strategy
Main Framework (Base)
├── Video Surveillance Sub-app (Independent repo, Vue3)
├── Access Control Management Sub-app (Independent repo, React18)
├── Alarm Center Sub-app (Independent repo, Vue3)
└── Data Analysis Sub-app (Independent repo, Vue3 + ECharts)
6.2 Shared WebSocket Connection
The biggest pitfall of micro-frontends is: multiple sub-apps each establish their own WebSocket connections, causing the number of connections to double.
// Base app maintains a single WebSocket, sub-apps subscribe via an event bus
class SharedWebSocket {
constructor(url) {
this.ws = new WebSocket(url);
this.channels = new Map(); // channel -> Set<callback>
this.ws.onmessage = (event) => {
const { channel, data } = JSON.parse(event.data);
this.channels.get(channel)?.forEach((cb) => cb(data));
};
}
subscribe(channel, callback) {
if (!this.channels.has(channel)) {
this.channels.set(channel, new Set());
}
this.channels.get(channel).add(callback);
return () => this.channels.get(channel).delete(callback); // Return unsubscribe function
}
}
// Mount to global, sub-apps access via window.__SHARED_WS__
window.__SHARED_WS__ = new SharedWebSocket('wss://iot.example.com/ws');
7. Security and Permissions
The security requirements of an IoT platform are higher than those of ordinary web applications:
- Video stream authentication: Playback URLs must carry short-lived tokens, automatically disconnecting the stream upon expiration.
- Operation auditing: All device control operations (unlocking, restarting cameras) need to record operation logs.
- Data masking: Some sensitive device data requires field-level masking based on user roles.
- Watermarking: Overlay the user's employee ID watermark on the video preview to prevent screenshot leaks.
// Video watermark overlay (Canvas solution)
function addWatermark(videoEl, userInfo) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const draw = () => {
canvas.width = videoEl.videoWidth;
canvas.height = videoEl.videoHeight;
ctx.drawImage(videoEl, 0, 0);
ctx.font = '16px monospace';
ctx.fillStyle = 'rgba(255,255,255,0.5)';
ctx.fillText(`${userInfo.name} ${userInfo.id}`, 10, 20);
ctx.fillText(new Date().toLocaleString(), 10, canvas.height - 10);
requestAnimationFrame(draw);
};
videoEl.addEventListener('play', draw, { once: true });
}
Summary
The core contradiction in frontend development for Hikvision's IoT platform is limited browser resources vs. massive real-time data. This article covered several of the most typical technical scenarios:
- Video stream playback: WebRTC low-latency solution + HLS playback solution, multi-split screen resource management
- Device state management: WebSocket message buffering + batch updates + virtual list on-demand rendering
- Real-time data visualization: ECharts incremental updates + map cluster points
- Long-running stability: Unified resource management + memory monitoring auto-refresh
- Micro-frontend architecture: Sub-app splitting + shared WebSocket connection
- Security and permissions: Stream authentication + watermarking + operation auditing
IoT frontend is an underestimated technical direction — it is not as glamorous as consumer-facing products, but its technical depth and engineering complexity are no less. If you are also working in the frontend direction of security, industrial internet, or smart cities, you are welcome to exchange ideas.
Writing Statement: This article is based on frontend technical practices in Hikvision's IoT business scenarios. The technical solutions involved have industry generality. The code examples in the article are illustrative implementations; for specific APIs, refer to the Hikvision Open Platform documentation.