aptpod Tech Blog

株式会社アプトポッドのテクノロジーブログです

intdashによるリアルタイム通信【後編】 - 数十行でかんたんビデオチャット実装

はじめに

前回の記事 では、Webアプリで映像を扱う際の産業用途におけるリアルタイムコミュニケーション実現のための検討ポイントと、intdashとintdash-RTC SDKという選択肢を紹介しました。

要点をおさらいすると:

  • SFU選定時はベンダー固有の実装に依存する点も考慮が必要
  • センサーデータとの統合や録画・解析は追加の設計が必要になる可能性
  • intdashは、産業用途に適したもう一つの選択肢

前編では概要しか触れなかったため、実際の使用感について興味をお持ちいただいた方もいらっしゃると思います。

本記事では、intdash-RTC SDKの具体的な実装方法をサンプルコード付きで解説します。環境が準備できていれば、約30分で動作するビデオチャットが完成しますので、ぜひお試しください。

ライブラリの概要

intdashは映像・音声・センサーデータを統合して伝送できるプラットフォームです。intdash-RTC SDKは、intdashで扱うデータのうち映像・音声をWebアプリから簡単に扱えるようにするライブラリです。

本ライブラリは3つのインターフェースで構成されています。

┌─────────────────────────────────────────────┐
│           MediaConnection                   │  統合管理
├─────────────────────┬───────────────────────┤
│    MediaSender      │    MediaReceiver      │  送受信
└─────────────────────┴───────────────────────┘
             |                   ^
             v                   |
┌─────────────────────────────────────────────┐
│              intdash (iSCP)                 │  伝送
└─────────────────────────────────────────────┘
  • MediaConnection: 送受信を統合管理。start()/stop()で接続制御
  • MediaSender: ローカルメディアをintdashに送信
  • MediaReceiver: intdashからメディアを受信し、video要素に直接接続

本ライブラリは、TypeScript 向けのintdash通信ライブラリ iscp-ts のラッパーとしても利用できますし、今回のようなアプリケーションでは接続設定を直接指定するシンプルな使い方も可能です。

実装ステップ

以下では、基本的なビデオチャットを実装するサンプルコードを5つのステップに分けて紹介します。全量は記事末尾の「Appendix: サンプルコード全文」に再掲しています。

1. ライブラリのimportとMediaConnectionの作成

// 1. ライブラリのimport
import { createMediaConnection } from "@aptpod/intdash-rtc";

// 2. MediaConnectionの作成(intdashへの接続も内部で行います)
const { mediaConnection } = await createMediaConnection(
  {
    address: "your-intdash.example.jp", // intdash接続アドレス
    projectUuid: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // intdashプロジェクトのUUID
    nodeId: "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy", // 送信元(自分側)のノードUUID
    enableTLS: true,
    apiToken: "your_token_here", // intdash Web Consoleから作成したAPIトークン(OAuth2サインインを使う場合はapiTokenを省略)
  },
  {
    sender: {
      // 音声に関する設定
      audio: {
        codec: "PCM", // PCM, OPUS, AACをサポート
      },
      // 映像に関する設定
      video: {
        codec: "H264", // H264, VP9をサポート
      },
    },
    receiver: {
      sourceNodeIds: ["zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz"], // 通信相手のノードUUID
    },
  },
);

@aptpod/intdash-rtcが今回のintdash-RTC SDKです。これをimportするだけで、intdashを介したビデオチャットが実装できます(@aptpod/iscp-tsは peer dependency として併せてインストールが必要です)。

createMediaConnection関数で、intdashへの接続とMediaConnectionの作成をまとめて行います。第1引数で接続情報(アドレス・プロジェクトUUID・ノードUUID・APIトークン)を、第2引数で送受信オプションを指定します。これらの値はintdash Web Consoleから取得できます。APIトークンは長期間有効な固定トークンですが、より安全なワンタイムトークンやOAuth2による認証も利用可能です。送受信オプションは階層化された構造になっており、sender.audioで音声設定、sender.videoで映像設定、receiverで受信対象ノードを指定します。コーデックは型安全な文字列リテラル型で指定でき、設定ミスを防ぎます。

2. ローカルメディアの取得と表示

// 3. ローカルメディアストリームの取得と設定
const localStream = await navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true,
});
await mediaConnection.sender.setLocalMediaStream(localStream);

// ローカル映像をvideo要素に表示
const localVideoEl = document.getElementById("local-video") as HTMLVideoElement;
localVideoEl.srcObject = new MediaStream(localStream.getVideoTracks());
localVideoEl.muted = true;
await localVideoEl.play();

getUserMediaでカメラとマイクからストリームを取得し、setLocalMediaStreamで設定します。取得したストリームはそのままローカルのvideo要素にも表示できます。

3. 受信メディアのvideo要素への接続

// 4. 受信メディアストリームの処理
const remoteVideoEl = document.getElementById(
  "remote-video",
) as HTMLVideoElement;
mediaConnection.receiver.on("statechange", (state) => {
  if (state === "running") {
    // 受信が開始されたら、video要素に直接接続
    mediaConnection.receiver.attach(remoteVideoEl);
  } else if (state === "stopped" || state === "failed") {
    // 受信が停止したら、video要素から切断
    mediaConnection.receiver.detach(remoteVideoEl);
  }
});

受信側の状態変化はstatechangeイベントで監視します。runningになったらattach()でvideo要素に直接接続できます。内部の変換処理(デコード等)はintdash-RTC SDKが処理するため、意識する必要がありません。

4. 接続開始

// 5. 接続開始(running状態まで待機)
await mediaConnection.start({ waitUntil: "running" });
console.log("接続が確立しました");

start({ waitUntil: 'running' })で接続を開始し、running状態になるまで待機します。これにより、接続が完全に確立してから次の処理に進めます。

5. UI操作と停止処理

// 6. UI操作(映像・音声の有効/無効切り替え)
document.getElementById("toggle-video")?.addEventListener("click", async () => {
  const enabled = await mediaConnection.sender.setVideoEnabled(
    !mediaConnection.sender.videoEnabled,
  );
  console.log(`映像: ${enabled ? "有効" : "無効"}`);
});

document.getElementById("toggle-audio")?.addEventListener("click", async () => {
  const enabled = await mediaConnection.sender.setAudioEnabled(
    !mediaConnection.sender.audioEnabled,
  );
  console.log(`音声: ${enabled ? "有効" : "無効"}`);
});

// 7. 停止処理(クリーンアップ)
document.getElementById("stop")?.addEventListener("click", async () => {
  await mediaConnection.stop();
  await mediaConnection.iscpConn.close();
  localStream.getTracks().forEach((track) => track.stop());
  console.log("接続を終了しました");
});

setVideoEnabled / setAudioEnabledで映像・音声を個別に制御できます。終了時はstop()でメディア処理を停止し、iscpConn.close()でintdash接続もクローズします。内部のワークレットやバッファも自動的にクリーンアップされます。

実際の動作の確認

このように、WebRTCと遜色ない簡潔さで、intdashを使った映像・音声通信を実装できます。

実際にサンプルアプリを動作させた様子が以下の動画です。

youtu.be

画面の左側がサンプルアプリ「Easy Video Chat」、右側がintdashに付属する簡易可視化ツール「Edge Finder」です。

サンプルアプリ「Easy Video Chat」には「あなた」(左)と「相手」(右)の2つの映像表示があります。今回のデモでは送信元と受信元のノードを同一に設定しているため、左右に同じ映像が表示されています。左の「あなた」はgetUserMediaで取得したローカル映像をそのまま表示したもの、右の「相手」はそのローカルメディアをintdashへ送信したあと再度受信してデコード・表示したものです。

実際に二者間で通話する場合は、相手側でも同様にintdashへの接続と受信元ノードの指定を行うことで、双方向での映像・音声通話が成立します。

同時にEdge Finder側では、送信されたH.264映像フレームとPCM音声データが、それぞれ時系列のデータポイントとしてintdashに記録されていく様子が確認できます。

さらなる拡張性

データの可視化とダッシュボードのカスタマイズ

動画で紹介した「Edge Finder」はintdashに標準で付属しており、送受信されているデータをすぐに確認できる簡易ツールです。

より高度な可視化やダッシュボードのカスタマイズが必要な場合は、弊社の可視化アプリケーション「VM2M Data Visualizer」をご利用いただけます。ノーコードで時系列データのダッシュボードを構築でき、映像とセンサーデータを時刻同期させた再生や、多彩なウィジェットを組み合わせた解析画面の作成に対応しています。

VM2M Data Visualizer: 複数の映像と時系列データを時刻同期して再生するタイムラインビュー

VM2M Data Visualizer: ゲージ・グラフ・レーダーチャートなど多彩なウィジェットを組み合わせたカスタムダッシュボード

Appendix: サンプルコード全文

ここまでステップごとに分割して紹介してきたサンプルコードの全量を、以下に再掲します。

// 1. ライブラリのimport
import { createMediaConnection } from "@aptpod/intdash-rtc";

// 2. MediaConnectionの作成(intdashへの接続も内部で行います)
const { mediaConnection } = await createMediaConnection(
  {
    address: "your-intdash.example.jp", // intdash接続アドレス
    projectUuid: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // intdashプロジェクトのUUID
    nodeId: "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy", // 送信元(自分側)のノードUUID
    enableTLS: true,
    apiToken: "your_token_here", // intdash Web Consoleから作成したAPIトークン(OAuth2サインインを使う場合はapiTokenを省略)
  },
  {
    sender: {
      // 音声に関する設定
      audio: {
        codec: "PCM", // PCM, OPUS, AACをサポート
      },
      // 映像に関する設定
      video: {
        codec: "H264", // H264, VP9をサポート
      },
    },
    receiver: {
      sourceNodeIds: ["zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz"], // 通信相手のノードUUID
    },
  },
);

// 3. ローカルメディアストリームの取得と設定
const localStream = await navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true,
});
await mediaConnection.sender.setLocalMediaStream(localStream);

// ローカル映像をvideo要素に表示
const localVideoEl = document.getElementById("local-video") as HTMLVideoElement;
localVideoEl.srcObject = new MediaStream(localStream.getVideoTracks());
localVideoEl.muted = true;
await localVideoEl.play();

// 4. 受信メディアストリームの処理
const remoteVideoEl = document.getElementById(
  "remote-video",
) as HTMLVideoElement;
mediaConnection.receiver.on("statechange", (state) => {
  if (state === "running") {
    // 受信が開始されたら、video要素に直接接続
    mediaConnection.receiver.attach(remoteVideoEl);
  } else if (state === "stopped" || state === "failed") {
    // 受信が停止したら、video要素から切断
    mediaConnection.receiver.detach(remoteVideoEl);
  }
});

// 5. 接続開始(running状態まで待機)
await mediaConnection.start({ waitUntil: "running" });
console.log("接続が確立しました");

// 6. UI操作(映像・音声の有効/無効切り替え)
document.getElementById("toggle-video")?.addEventListener("click", async () => {
  const enabled = await mediaConnection.sender.setVideoEnabled(
    !mediaConnection.sender.videoEnabled,
  );
  console.log(`映像: ${enabled ? "有効" : "無効"}`);
});

document.getElementById("toggle-audio")?.addEventListener("click", async () => {
  const enabled = await mediaConnection.sender.setAudioEnabled(
    !mediaConnection.sender.audioEnabled,
  );
  console.log(`音声: ${enabled ? "有効" : "無効"}`);
});

// 7. 停止処理(クリーンアップ)
document.getElementById("stop")?.addEventListener("click", async () => {
  await mediaConnection.stop();
  await mediaConnection.iscpConn.close();
  localStream.getTracks().forEach((track) => track.stop());
  console.log("接続を終了しました");
});

おわりに

本記事では、intdash-RTC SDKを使ったビデオチャットの実装方法を見てきました。

ポイントのまとめ:

  • WebRTCと同等の簡潔さで、intdash経由のビデオチャットを実装可能

最初はビデオチャットから始めて、将来はセンサーデータを含むマルチモーダルシステムへ拡張できるのがintdashの強みです。同じプラットフォーム、同じSDKで対応できるため、技術選定で後悔するリスクを減らせます。

「試してみたい」「もう少し詳しく知りたい」という方は、ぜひお気軽にお問い合わせください。本ライブラリは現在お問い合わせベースでの個別提供となっています。