Back to Work
2024Full-Stack Web Application

CCTV Kota Malang - Real-Time Traffic Surveillance

CCTV Kota Malang - Real-Time Traffic Surveillance
About

Real-time multi-camera surveillance dashboard that enhances Malang city's official CCTV system with HLS streaming, drag-and-drop layouts, interactive maps, video capture, and a multi-region relay infrastructure to bypass upstream IP blocks.

Technologies
Next.js 14TypeScriptTailwind CSSZustandVideo.jsHLS.js@dnd-kitReact LeafletShadcn/uiCloudflare WorkersFly.ioTauri v2Rust
Live ProjectVisit Website

CCTV Kota Malang - Real-Time Traffic Surveillance

CCTV Kota Malang transforms the city's official single-camera CCTV viewing experience into a powerful multi-camera surveillance dashboard. I built this application to provide citizens and traffic monitors with a more efficient way to view multiple traffic cameras simultaneously, featuring customizable layouts, video capture, and an interactive map interface.

Project Overview

Malang city operates dozens of CCTV cameras across major intersections and streets, but the official system only allows viewing one camera at a time. This limitation makes it difficult for users to monitor multiple locations or compare traffic conditions across different areas. CCTV Kota Malang solves this by aggregating all available camera feeds into a unified, user-friendly dashboard.

What started as a simple Next.js frontend has evolved into a multi-layered system — the upstream server progressively blocked cloud provider IPs, which led to building a Cloudflare Worker orchestrator with multi-region Fly.io relays, automatic failover, and Telegram alerting. The project also expanded to native desktop with a Tauri app and a macOS menu bar companion.

Key Features

Multi-Camera Grid View

A responsive grid system that displays multiple live camera feeds simultaneously:

  • Configurable Layout: Adjust from 1 to 6 columns based on preference.
  • Drag-and-Drop Reordering: Rearrange cameras using intuitive drag-and-drop powered by @dnd-kit.
  • Persistent Selection: Camera selections and order are saved to localStorage across sessions.
  • Real-Time Streaming: HLS streams delivered through Video.js with optimized playback settings.

Interactive Map Selection

An OpenStreetMap-powered interface for discovering cameras:

  • Geographic Overview: View all camera locations plotted on an interactive Leaflet map centered on Malang city.
  • Visual Indicators: Blue markers for available cameras, red markers for selected ones.
  • Touch-Enabled: Full gesture support for mobile users including pinch-to-zoom.
  • Quick Selection: Click any marker to instantly add that camera to your grid.

Video Capture Suite

Built-in tools for capturing and saving footage:

  • Instant Snapshots: One-click screenshot capture saved as timestamped PNG files.
  • 10-Second Recording: Record short video clips with smart codec selection (MP4/WebM fallback chain).
  • Visual Feedback: Recording indicator with countdown timer and pulsing red badge.
  • Cross-Browser Support: Automatic format detection for maximum compatibility.

Desktop Applications

Native desktop experiences built with Tauri v2:

  • Full Desktop App: Tauri-wrapped Next.js app with an embedded Rust proxy server, available for macOS, Windows, and Linux.
  • Menu Bar Companion: A lightweight macOS tray app for quick single-camera monitoring — frosted glass popup, no dock icon. Published as a separate open-source project.

Stream Proxy Architecture

The most technically interesting part of this project is the streaming infrastructure. The upstream CCTV server progressively blocked cloud provider IP ranges, requiring an evolving proxy strategy.

The IP Blocking Journey

  1. Cloudflare Workers (fetch()) — HTTP proxy at the edge, directly to upstream. Blocked — entire CF IP range (AS13335) blacklisted by WAF.
  2. Cloudflare Workers (TCP socket) — Bypassed HTTP layer using connect() for direct TCP. Still blocked — same IP range.
  3. AWS Lambda (Singapore) — Moved to AWS IP range via API Gateway. Blocked after a few weeks.
  4. Fly.io (Singapore) — Lightweight Node.js relay. Blocked.
  5. Fly.io (Tokyo) — Current active relay.

Current Architecture

Browser → Cloudflare Worker → Fly.io Relay → Upstream CCTV
              (orchestrator)    (3 regions)     (103.135.14.67)

Cloudflare Worker acts as the orchestrator:

  • Routes /api/stream/* requests to the active Fly.io relay.
  • Caches .ts video segments at the CF edge (Cache API) to reduce relay load.
  • No-cache policy for .m3u8 playlists to ensure live data.
  • Stores relay state in Workers KV with 60-second cache TTL.

Fly.io Relays are minimal Node.js HTTP servers deployed across 3 regions (Tokyo, Sydney, Singapore):

  • Inject required Host, Referer, and User-Agent headers.
  • Accept the upstream's self-signed TLS certificate.
  • Expose a /health endpoint that probes a real stream.

Automatic Failover: When a relay returns 403 (blocked), the Worker tries the next region, updates KV state, and sends a Telegram notification. All subsequent requests go to the new active relay immediately.

Health Check Cron: Runs every 30 minutes via Workers cron trigger, probes all relays, and sends Telegram alerts only on status changes. Rate-limited critical alerts (1 per hour) prevent notification spam when all relays are down.

Technical Architecture

Frontend (Next.js 14)

The application leverages Next.js App Router for optimal performance:

  • Framework: Next.js 14 with Server and Client Components.
  • State Management: Zustand stores with localStorage persistence middleware.
  • Video Playback: Video.js 8 with HLS.js for HTTP Live Streaming protocol support.
  • Drag & Drop: @dnd-kit with closestCorners collision detection and mixed-axis support.
  • Maps: React Leaflet with OpenStreetMap tiles for zero-cost geographic visualization.
  • Styling: Tailwind CSS with Shadcn/ui components built on Radix UI primitives.
  • Animations: Framer Motion for scroll-triggered reveals and micro-interactions.

API Layer

  • /api/cameras: Proxies the official camera list API with proper typing.
  • /api/stream/[...path]: Routed through Cloudflare Worker to Fly.io relays.
  • /api/og: Edge-runtime Open Graph image generation for social sharing.

Stream Infrastructure

  • Cloudflare Workers: TypeScript, KV for state, Cache API for segments, cron triggers.
  • Fly.io: Node.js relay servers across 3 regions with Dockerized deployments.
  • Telegram Bot: Automated alerts for failover events and health check results.

Desktop (Tauri v2)

  • Full App: Tauri wrapping the Next.js frontend with an embedded Rust/Axum proxy server.
  • Menu Bar App: Standalone Tauri tray app with vanilla HTML frontend and its own Rust proxy.
  • CI/CD: GitHub Actions builds for macOS (aarch64 + x86_64), Ubuntu, and Windows.

Development & Deployment

  • Runtime: Bun with Volta-managed Node.js 22.
  • Linting: Biome for fast, unified formatting and linting.
  • Web Hosting: Vercel (Next.js) + Cloudflare (Worker + DNS).
  • Relay Hosting: Fly.io (multi-region).

Design Philosophy

CCTV Kota Malang follows a mobile-first responsive approach with careful attention to real-world usage patterns:

  • Grid-First Layout: The multi-camera grid is the primary interface, optimized for quick scanning.
  • Minimal Chrome: Streamlined UI that keeps the focus on video content.
  • Dark Mode Support: Full theme system via next-themes for comfortable viewing in any lighting.
  • Touch Optimized: Large tap targets and gesture support for mobile users monitoring on-the-go.

Key design decisions:

  • Responsive Columns: Grid automatically adjusts from 1-6 columns based on viewport and user preference.
  • Non-Blocking UI: Recording and capture operations run asynchronously with visual progress indicators.
  • Accessible Controls: Full keyboard navigation and ARIA labels throughout the interface.

Implementation Highlights

Multi-Region Relay Failover

// Try active relay first, then failover to others on 403
for (const relay of orderedRelays) {
  const result = await fetchViaRelay(relay, streamPath);
  if (result.status === 403) continue; // IP blocked, try next
  return { result, relay, switched: i > 0 };
}
throw new Error('All relays failed');

Edge Segment Caching

// Cache immutable .ts segments at CF edge, skip volatile .m3u8 playlists
if (isSegment) {
  const cached = await caches.default.match(cacheKey);
  if (cached) return cached;
}
// After fetch, cache the segment
ctx.waitUntil(caches.default.put(cacheKey, resp.clone()));

HLS Stream Optimization

// Optimized Video.js configuration for faster stream start
hls: {
  enableLowInitialPlaylist: true,
  overrideNative: true,
}

The player prioritizes lower-quality playlist variants initially for faster first-frame rendering, then adapts up as bandwidth allows.

Smart Codec Fallback

The recording feature implements a codec priority chain for maximum compatibility:

  1. MP4 (H.264/AVC): Preferred for social media sharing and universal playback.
  2. WebM (VP9): High-quality fallback for modern browsers.
  3. WebM (VP8): Broader compatibility fallback.
  4. Generic WebM: Last resort for older browsers.

SEO & Performance

The application is optimized for local Indonesian search queries:

  • Target Keywords: "CCTV Online Kota Malang", "Live Streaming CCTV Malang", "Pantau Lalu Lintas Malang"
  • Dynamic OG Images: Per-camera pages generate unique social preview images.
  • Structured Metadata: Comprehensive meta tags with Indonesian locale support.
  • Fast Initial Load: Static shell with dynamic data hydration.

Results

CCTV Kota Malang has grown from a simple frontend into a production system that handles real-world infrastructure challenges. The multi-region relay architecture keeps streams available despite aggressive upstream IP blocking, automatic failover ensures minimal downtime, and Telegram alerting provides visibility into system health. The project spans web, desktop, and menu bar — giving users the right tool for their monitoring needs.

Next ProjectView All Work