post-cover

2025-07-07 技術ブログ

【第1回】【Node.js + React】PTZ カメラを Web ブラウザから操作!リアルタイム制御システムの開発

本記事で紹介するプログラムは以下の GitHub リポジトリで公開しています

GitHub - isec-promotion/ptz-controller-local

はじめに

今回は、社内で開発した「ISAPI 対応 PTZ カメラ制御システム」について、その技術的な裏側を詳しくご紹介します。

本記事では、システム開発の背景から、技術スタックの選定理由、そして実装の核となるリアルタイム映像配信や PTZ 制御の仕組みまで、開発で得られた知見を余すところなくお伝えします。


開発の背景:お客様の声から生まれたシステム

開発のきっかけは、お客様からいただいたあるご要望でした。

「ISAPI 対応カメラの多くは、ブラウザで管理画面を開いたり、専用アプリを使えば PTZ 操作やライブビューができます。しかし、私たちが求めているのは、それらの専用画面ではなく、自社製品のダッシュボードに組み込んで、そこからシームレスにライブビューの確認と PTZ 操作をすることなんです。」

このご要望を受け、私たちはWeb ブラウザから直接ライブビューの表示と PTZ 操作が完結するシステムの開発に着手しました。特定の管理画面や専用アプリケーションに縛られることなく、あらゆる Web アプリケーションに柔軟に組み込めることを目指したのです。

このシステムで実現できること

このシステムを使えば、PC はもちろん、スマートフォンやタブレットからも、Web ブラウザさえあればどこからでも PTZ カメラを直感的に操作できます。主な機能は以下の通りです。

  • ライブ映像表示: RTSP ストリームを低遅延で Web ブラウザに表示します。
  • 直感的な PTZ 制御: 画面上のボタンを押している間だけカメラが動き、指を離せばピタッと止まる、ストレスのない操作性を実現しました。8 方向の移動に対応しています。
  • プリセット機能: よく使う画角を登録しておき、ワンクリックで「原点」に復帰させることができます。
  • レスポンシ IVE デザイン: デバイスの画面サイズに合わせて UI が最適化されます。
  • リアルタイム接続状態表示: カメラとの接続状況が一目でわかります。

システム構成と技術選定のこだわり

本システムの全体像は、API サーバーとストリーミングサーバーからなる Node.js のバックエンドと、UI を担当する React のフロントエンドで構成されています。

ptz-controller/
├── backend/          # Node.js + TypeScript APIサーバー
│   ├── index.ts      # PTZ制御API(Hono)
│   ├── stream-server.ts  # RTSPストリーミングサーバー
│   └── package.json
├── frontend/         # React + TypeScript Webアプリ
│   ├── src/
│   │   ├── App.tsx
│   │   └── components/
│   │       ├── PTZControls.tsx      # PTZ制御パネル
│   │       └── RTSPVideoPlayer.tsx  # 映像表示コンポーネント
│   └── package.json
└── .env              # 環境設定ファイル

なぜこの技術を選んだのか?

【Backend】 Node.js, Hono, WebSocket, FFmpeg

  • Node.js + TypeScript: 非同期処理に強く、リアルタイム通信との相性が良いため、バックエンドの基盤として採用しました。

  • Hono: API サーバーのフレームワークには、高速かつ軽量で知られる Hono を選択しました。Express.js ライクな書き方ができながらも、パフォーマンスに優れている点が魅力です。

  • FFmpeg & WebSocket: PTZ カメラの映像は多くの場合 RTSP という形式で配信されていますが、これを直接 Web ブラウザで再生するのは簡単ではありません。そこで、サーバーサイドで万能なマルチメディアツール FFmpeg を使い、RTSP ストリームを Web で扱いやすい MJPEG (Motion JPEG) 形式にリアルタイムで変換。そして、変換した映像データを WebSocket を通じてフロントエンドに低遅延でプッシュ配信する構成としました。

  • Digest 認証: 多くの業務用カメラで採用されている ISAPI の認証方式は、一般的な Basic 認証とは異なる Digest 認証 です。

【Frontend】 React, Vite, Tailwind CSS, Canvas API

  • React + TypeScript: コンポーネントベースで UI を効率的に構築できる React は、今回のプロジェクトに最適でした。PTZ 制御パネルや映像プレイヤーといった部品を個別に開発・管理できます。

  • Vite: 開発サーバーの起動やビルドが非常に高速な Vite を採用し、開発体験を向上させました。修正が即座にブラウザに反映されるため、ストレスなく開発に集中できます。

  • Tailwind CSS: ユーティリティファーストのアプローチで、デザイン性の高い UI を迅速に構築するために採用しました。レスポンシブデザインへの対応も容易です。

  • Canvas API: バックエンドから WebSocket で送られてくる MJPEG の映像データを描画するために、HTML5 の Canvas API を使用しています。<img> タグで表示するよりも、きめ細やかな描画制御が可能です。

実装のハイライト:どうやって動かしているのか?

1. ライブ映像の表示 (RTSP → MJPEG → WebSocket → Canvas)

このシステムの心臓部とも言えるライブ映像表示は、以下の流れで実現しています。

  1. Node.js サーバーが、子プロセスとしてFFmpegを起動します。
  2. FFmpegは、設定されたカメラの RTSP ストリーム (rtsp://...) を受信します。
  3. 受信した映像を、-f mjpeg オプションで MJPEG 形式にリアルタイム変換します。
  4. 変換された MJPEG のデータは、WebSocket サーバー (stream-server.ts) に送られます。
  5. ブラウザ(フロントエンド)は WebSocket サーバーに接続し、送られてくる映像データを待ち受けます。
  6. React コンポーネント (RTSPVideoPlayer.tsx) は、受信した JPEG データを Canvas 上に次々と描画し、動画として見せています。

この構成により、ブラウザのプラグインに依存することなく、クロスプラットフォームな映像表示を実現しました。

2. リアルタイム PTZ 制御

PTZ 制御で最もこだわったのは「ボタンを押している間だけ動く」という直感的な操作性です。

これは、React コンポーネント (PTZControls.tsx) でマウスやタッチのイベントを利用して実現しています。

  • onMouseDown / onTouchStart: ユーザーが方向ボタンを押し始めた瞬間に、バックエンドの /api/ptz/move エンドポイントにリクエストを送信します。「上に動け」「スピードは 50 で」といった情報を JSON で送ります。
  • onMouseUp / onMouseLeave / onTouchEnd: ユーザーがボタンから指を離した瞬間に、/api/ptz/stop エンドポイントにリクエストを送信し、カメラの動きを停止させます。

バックエンドの Hono サーバー (index.ts) は、これらのリクエストを受け取ると、環境変数に設定されたカメラ情報と Digest 認証を用いて、カメラの ISAPI エンドポイントに HTTP リクエストを転送します。これにより、カメラが物理的に動作するという仕組みです。

// リクエスト例:右下にスピード50で移動を開始
POST /api/ptz/move
{
  "direction": "downRight",
  "speed": 50
}

セットアップ方法

ご自身の環境でこのシステムを試してみたい方は、以下の手順でセットアップできます。

前提条件

  • Node.js 18 以上
  • FFmpeg(PC にインストール済みであること)
  • ISAPI 対応の PTZ カメラ

1. リポジトリのクローン

git clone https://github.com/isec-promotion/ptz-controller-local.git
cd ptz-controller-local

2. 環境設定 プロジェクトのルートに .env ファイルをコピーして作成し、ご自身のカメラ情報や環境に合わせて内容を編集します。

# カメラ設定
CAMERA_IP=192.168.1.100
CAMERA_USERNAME=admin
CAMERA_PASSWORD=your_password

# サーバー設定
BACKEND_PORT=3001
STREAM_WS_PORT=8080
CORS_ORIGIN=http://localhost:5173

# ISAPI設定 (カメラの仕様に合わせて変更)
ISAPI_BASE_PATH=/ISAPI
MJPEG_STREAM_PATH=/Streaming/channels/101/httppreview

3. 依存関係のインストール

# Backend
cd backend
npm install

# Frontend
cd ../frontend
npm install

4. 開発サーバーの起動 2 つのターミナルを開いて、それぞれ以下のコマンドを実行します。

# ターミナル1: Backendサーバー (API & Stream)
cd backend
npm run dev:all

# ターミナル2: Frontendサーバー
cd frontend
npm run dev

これで準備は完了です!ブラウザで http://localhost:5173 にアクセスしてみてください。

まとめと今後の展望

今回は、Node.js と React を用いて、Web ブラウザから PTZ カメラをリアルタイムに制御するシステムをご紹介しました。RTSP ストリームのハンドリングや ISAPI との連携など、いくつかの技術的な挑戦がありましたが、モダンな Web 技術を組み合わせることで、非常に使いやすく拡張性の高いシステムが実現できたと考えています。

今後は、

  • 録画機能の実装
  • 複数カメラの切り替え対応
  • プリセットの追加・編集機能

といった機能拡張も検討しています。

この記事が、同じような課題を持つ開発者の方々や、Web 技術の新たな活用法を探している方々の参考になれば幸いです。

本記事で紹介するプログラムは以下の GitHub リポジトリで公開しています

GitHub - isec-promotion/ptz-controller-local