跪拜 Guibai
← Back to the summary

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:

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:

  1. 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.
  2. 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.
  3. 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:

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:

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:

  1. Video stream authentication: Playback URLs must carry short-lived tokens, automatically disconnecting the stream upon expiration.
  2. Operation auditing: All device control operations (unlocking, restarting cameras) need to record operation logs.
  3. Data masking: Some sensitive device data requires field-level masking based on user roles.
  4. 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:

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.