DEV Community

Cover image for Multi-camera monitoring dashboard architecture (slots, tokens, teardown)
Imou-OpenPlatform
Imou-OpenPlatform

Posted on

Multi-camera monitoring dashboard architecture (slots, tokens, teardown)

How can I build a multi-camera monitoring dashboard? Model three layers: a device ledger (listDeviceDetailsByPage + your site map), a fixed set of slots (not one player per camera in the estate), and short-lived play credentials (getKitToken + ImouPlayer, or bindDeviceLive HLS). Default tiles to SD (streamId = 1), fetch streams only when a slot is open, cache kitToken on the BFF (not accessToken in the browser), and destroy players on close. Live, playback, PTZ, and two-way talk remain per-device capabilities—don’t put joysticks and mics on every cell.

This is an architecture essay. Bandwidth rants and official FAQ pages exist elsewhere; here we care about state machines and teardown.

Why dashboards fail as “just N video tags”

A dashboard is a concurrency product. If you spawn a player for every row in listDeviceDetailsByPage at login, you will hit some combination of:

  • Browser decode/CPU collapse

  • Office uplink saturation (especially HD, streamId = 0)

  • Live-view quota exhaustion

  • Stream prefetch 404s / timeouts because sources were requested before the tile existed

The fix is not a bigger instance type. The fix is slots.

Architecture

┌─────────────────────────────────────────────┐
│  UI: site picker · N slots · 1 focus pane   │
└──────────────────┬──────────────────────────┘
                   │ session cookie only
                   ▼
┌─────────────────────────────────────────────┐
│  BFF: ACL · accessToken · kitToken/URL cache│
└──────────────────┬──────────────────────────┘
                   ▼
┌─────────────────────────────────────────────┐
│  OpenAPI: listDeviceDetailsByPage           │
│           getKitToken | bindDeviceLive      │
└──────────────────┬──────────────────────────┘
                   ▼
              cameras / cloud live
Enter fullscreen mode Exit fullscreen mode
Layer Responsibility Anti-pattern
Ledger Which cameras exist; tenant/site mapping; capability flags Hard-coded serials; assuming consumer app = OpenAPI pool
Slots Max concurrent live sessions per user/page Infinite virtual list of live players
Players Bind credential ↔ DOM node; destroy on exit Hidden tabs still decoding HD

Optional focus pane: one HD (streamId = 0) + PTZ/talk/playback chrome if capability and role allow. Walls stay SD.

Slot state machine

Each slot is a small FSM. Names are yours; the transitions matter.

EMPTY
  --assign(deviceId)--> ASSIGNED          // no network yet
  --open()------------> FETCHING_CRED     // BFF mint
  --cred_ok-----------> PLAYING
  --cred_fail---------> ERROR
PLAYING
  --pause/visibility--> PAUSED            // optional: stop decode
  --swap(deviceId)----> TEARING_DOWN → FETCHING_CRED
  --close()-----------> TEARING_DOWN → EMPTY
ERROR
  --retry/close-------> FETCHING_CRED or EMPTY
Enter fullscreen mode Exit fullscreen mode

Rule: ASSIGNED must not call getKitToken / bindDeviceLive. Operators drag cameras onto a 3×3 grid all morning; minting nine tokens per drag is how you invent outages.

Rule: Light Application / player guidance: don’t request stream sources early. Fetch when the slot enters FETCHING_CRED because the tile is visible and the user intends to watch.

Token model

accessToken     → OpenAPI on BFF only
kitToken        → ImouPlayer in the browser
live HLS/RTMP   → URL clients; secret
appSecret       → vault
Enter fullscreen mode Exit fullscreen mode

kitToken accessToken. Document this in the dashboard README. Player docs: JS SDK. Practical cache: ~1 hour on BFF; TTL ~2 hours—re-mint on 401-ish player errors instead of stuffing admin tokens into Wasm.

Per-slot credential cache key:

(userId or session, deviceId, channelId, streamId, mode=player|hls)
Enter fullscreen mode Exit fullscreen mode

Do not share one kitToken across tenants. Do not reuse a live URL from Tenant A’s kiosk on Tenant B’s wall.

API timing

T0  Page load
    GET /ledger?site=   → list join, no live calls

T1  User fills 4 slots (ASSIGNED)
    still no getKitToken / bindDeviceLive

T2  Slot viewport visible / user hits Play
    POST /live-session { deviceId, streamId: 1 }
    → ACL
    → getKitToken OR bindDeviceLive

T3  Player init / HLS attach

T4  User closes slot or leaves page
    player.destroy()
    POST /live-session/end   // unbind if you created a live object
    drop cached cred for that slot
Enter fullscreen mode Exit fullscreen mode

List API: listDeviceDetailsByPage (page sizes as documented—don’t assume unbounded).

Live methods: bindDeviceLive, live summary. RTMP (createDeviceRtmpLive) is rarely the wall tile; keep it off the mosaic unless you have a real media reason.

Quota: My Resources. Cap N in the UI (4 or 9 is a product decision, not a platform constant). No invented Mbps SLA.

Pseudo-code (client)

const MAX_SLOTS = 9;

function createSlot() {
  return { state: "EMPTY", deviceId: null, player: null };
}

async function openSlot(slot, deviceId, bff) {
  if (slot.player) await teardown(slot);
  slot.deviceId = deviceId;
  slot.state = "FETCHING_CRED";
  const { kitToken } = await bff.liveSession({
    deviceId,
    streamId: 1, // SD wall default
  });
  slot.player = initImouPlayer({ kitToken }); // NOT accessToken
  slot.state = "PLAYING";
}

async function teardown(slot) {
  slot.state = "TEARING_DOWN";
  try {
    slot.player?.destroy?.();
  } finally {
    await bff.endSession({ deviceId: slot.deviceId }).catch(() => {});
    slot.player = null;
    slot.deviceId = null;
    slot.state = "EMPTY";
  }
}

window.addEventListener("pagehide", () => slots.forEach(teardown));
document.addEventListener("visibilitychange", () => {
  if (document.hidden) slots.forEach(maybePauseDecode);
});
Enter fullscreen mode Exit fullscreen mode

Wire initImouPlayer to current SDK options (WasmLibPath, streamId, etc.). The architecture point is lifecycle, not a frozen constructor.

Capability chrome (keep it off the mosaic)

Control Wall slot Focus pane
Live SD Yes Yes
Live HD No (or promote to focus) If role allows
PTZ No If capability + role
Two-way talk No If capability + role + intent
Playback / incident seek No If recording/package allows

Do not invent SKUs. Do not invent retention days. Do not use GB28181 as the international dashboard backbone.

Server-side slot registry (optional but useful)

If two browser tabs can open the same user’s wall, a pure client cap is leaky. A thin registry on the BFF helps:

key: userId + pageSessionId
value: set of active (deviceId, channelId) live sessions
cap: MAX_SLOTS (and maybe MAX_HD = 1)
Enter fullscreen mode Exit fullscreen mode

Mint fails with 429-equivalent when the set is full. End-session and pagehide must delete members. This is your product quota in front of platform live-view quota—not a replacement for My Resources.

Races: double-click Play, React Strict Mode double mount, and rapid slot swap. Make openSlot idempotent per slot id: abort the in-flight mint if teardown starts. Never leave an orphan ImouPlayer in the DOM.

SSR: don’t init Wasm players on the server. Hydrate the grid as EMPTY/ASSIGNED, then open on the client.

Failure modes to design for

Symptom Likely cause Product handling
Black screen accessToken in player; bad Wasm path Token checklist; SDK FAQ
404 on stream Prefetch before slot open; expired live object Fetch on open; re-bind
First tiles OK, later fail Quota / concurrency Cap slots; SD default
Joystick errors Fixed camera Hide PTZ from capability
Mic on tile 7 talks to aisle 2 Talk on wall Talk only on focus

Implementation checklist

  • [ ] Ledger sync job, ACL on every mint

  • [ ] MAX_SLOTS constant reviewed with ops

  • [ ] SD default; HD is a promotion

  • [ ] kitTokenaccessToken in code review

  • [ ] destroy on close, route change, pagehide

  • [ ] No prefetch of 16 live URLs at boot

  • [ ] Capability flags for PTZ/talk/playback

Build the dashboard against real APIs: start at open.imoulife.com. Imou Open Platform is cloud video and AIoT focused, with APIs, SDKs, and low-code components to help vendors and developers ship monitoring walls that stay within slots, tokens, and teardown—not sixteen immortal HD players.

Related: Video Monitoring · JS SDK

Top comments (0)