Tencent RTC Web SDK と Next.js を組み合わせてビデオ通話アプリをサクッと作ってみた

この記事は↓のアドベントカレンダーの参加記事です。

https://qiita.com/advent-calendar/2023/v-cube

TRTC Web SDK を使うと、サクッとビデオ通話を行うアプリケーションが作れる、という話を聞いたので、試しにドキュメント (https://web.sdk.qcloud.com/trtc/webrtc/doc/en/index.html) を読んでみてサンプルアプリ ([tomoasleep/hello-trtc](https://github.com/tomoasleep/hello-trtc)) を作ってみました。

![スクリーンショット 2023-12-22 16.40.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/4ecb3eb5-bb82-41fc-a79d-6e3ed184565f.png)

こういう感じのビデオ通話アプリを作ってみたのですが、バックエンドは基本 Tencent 側がやってくれて、フロントエンドを書くだけで簡単に動かせました :v: どういう感じで作れるか紹介していこうと思います。

## 1. Tencent Cloud 上でアプリケーションを設定する

ビデオ通話のバックエンドは Tencent Cloud を用いるのでまずはそれの設定を行います。

↓ からアカウントの作成やログインが行えるので、まずはアカウントを作成します。

https://www.tencentcloud.com/en/account/register?

アカウントを作成したら、 https://console.tencentcloud.com/trtc/app からアプリケーションを作成します。
package type は RTC Engine で、 application name は適当でOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/7f73b20d-c083-028e-3508-9a1d320bdb8c.png)

作成したら、アプリケーションの内容が表示されるので、 `SDKAppID`, `SecretKey` の値を覚えておきます。
Tencent Cloud 上ではこれだけで OK です。

2023/12 時点だと、アカウント作成時から1年間は毎月10000分無料利用できるようです。
Ref: https://www.tencentcloud.com/campaign/freetier

(※ https://console.trtc.io/overview からも設定等が可能です。こっちは RTC 関連に絞られているので分かりやすい)

## 2. Web フロントエンドの実装を行う

ということで、フロントエンドの実装を行います。公式で (非 React ですが) チュートリアルがあるので、これを参考にしつつ、 Next.js 向けに書いてみます。

https://web.sdk.qcloud.com/trtc/webrtc/doc/en/tutorial-11-basic-video-call.html

今回は [create-next-app](https://nextjs.org/docs/pages/api-reference/create-next-app) で作った Next.js 14 アプリケーションをベースに作ります。

今回実装したサンプルコードは、↓にあります。

https://github.com/tomoasleep/hello-trtc

### 2-1. ユーザーの signature を計算する Server Action を作る

TRTC アプリケーションに接続するコードは大まかに以下の通りになっています。

TRTC 側では Room とそれに参加している User の管理を行ってくれるので、クライアント側では User として Room に参加する、という形で接続が行えます。

```typescript
const client = TRTC.createClient({
  mode: "rtc",
  sdkAppId, // アプリケーションの SDKAppID
  userId, // ユーザーID
  userSig, // ユーザーの署名データ
  useStringRoomId: true,
});

await client.join({
  roomId // 部屋 ID
});
```

User の情報として必要なのが、 id と sig (署名) です。 TRTC 側では全 User の管理や登録は行わないので、 User の認証に sig を利用します。

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/cb4a4bba-c398-8e2a-fa71-875f979ad2b0.png)
> https://www.tencentcloud.com/document/product/647/35166 より

sig の生成には `SecretKey` を使うので、別でサーバーを立てて…ということになるのですが、ここでは [Server Action](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) を使ってみます。

今回は userId, roomId はユーザーに入力してもらって、それを元に Server Action で userSig を計算します。

```typescript
"use server";

import { Api } from "tls-sig-api-v2";

// sdkAppId はクライアントに公開してしまって OK
const sdkAppId = Number(process.env.NEXT_PUBLIC_TRTC_SDK_APP_ID);

export async function generateSig(userId: string): Promise<string> {
  // SecretKey は sig の生成に使うので、クライアントへの公開は NG
  const sdkSecretKey = process.env.TRTC_SECRET_KEY;
  if (!sdkSecretKey) {
    throw new Error("secret key is not given");
  }

  const api = new Api(sdkAppId, sdkSecretKey);

  return api.genSig(userId, 86400 /* 1day */);
}
```

```tsx
export default function Home() {
  // ...

  return (
    <LoginForm
      onSubmit={async ({ roomId, userId }) => {
        const userSig = await generateSig(userId);
        setUserSession({ userId, userSig });
        setRoomId(roomId);
      }}
    />
  );
```

### 2-2. 通話画面を作る

2-1 で作成した usersig と userId, roomId を元に接続と通話を行います。

接続に必要なコードは、チュートリアル (https://web.sdk.qcloud.com/trtc/webrtc/doc/en/tutorial-11-basic-video-call.html) などを参考にすると以下の通りになります。


```typescript
function connectTRTC({ userId, userSig, roomId, localStreamContainerId, remoteStreamContainerId }) {
  const client = TRTC.createClient({
    mode: "rtc",
    sdkAppId, // アプリケーションの SDKAppID
    userId, // ユーザーID
    userSig, // ユーザーの署名データ
    useStringRoomId: true,
  });

  client.on("stream-added", (event) => {
    const remoteStream = event.stream;
    client.subscribe(remoteStream);
  });

  client.on("stream-subscribed", (event) => {
    const remoteStream = event.stream;
    remoteStream.play(remoteStreamContainerId); // 指定した id の HTML 要素に、他の参加者を表示する
  });

  await client.join({ roomId });

  const localStream = TRTC.createStream({
    userId,
    audio: true,
    video: true,
  });
  await localStream.initialize();
  localStream.play(localStreamContainerId);  // 指定した id の HTML 要素に、自分を表示する
  await client.publish(localStream);
}
```

これを React 環境で使えるように useEffect などを用いて Hook 化して、 React コンポーネントから接続するようにします。

```tsx
export function VideoChat({ roomId, userId, userSig }) {
  const remoteStreamContainerId = useId();
  const localStreamContainerId = useId();

  const useTRTC({
    roomId,
    userId,
    userSig,
    localStreamContainerId,
    remoteStreamContainerId,
  });

  return (
    <div className="container mx-auto">
      <div id={localStreamContainerId} />
      <div id={remoteStreamContainerId} />
    </div>
  );
}
```

### 2-3. 接続デバイスを変更できるようにする

実際のビデオ通話では、通話に使いたいカメラやマイクを変更したいことがあります。
TRTC Web SDK では、デバイスのリストの取得や、変更も簡単に行うことが可能です。

以下のようなコードで接続デバイスを変更する機能を実装できます

```tsx
export function Selector({
  localStream,
  kind,
}: {
  localStream: LocalStream | null;
  kind: MediaDeviceKind;
}) {
  const { data: devices } = useAsyncValue(async () => {
    const devices = await TRTC.getDevices(); // デバイス一覧を取得
    return devices.filter((device) => device.kind === kind);
  });

  if (!devices || !localStream) {
    return <div>loading...</div>;
  }

  return (
    <div>
      <select
        onChange={(event) => {
          if (event.target.value) {
            localStream.switchDevice(
              kind === "videoinput" ? "video" : "audio",
              event.target.value
            ); // 通話に使うデバイスを設定
          }
        }}
      >
        {devices.map((device) => {
          return (
            <option key={device.deviceId} value={device.deviceId}>
              {device.label}
            </option>
          );
        })}
      </select>
    </div>
  );
}

```

という感じで作ったのが以下の画面です。 (アイコンで隠している部分が、実際にはカメラで録画している内容になります。)

![スクリーンショット 2023-12-22 16.40.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/4ecb3eb5-bb82-41fc-a79d-6e3ed184565f.png)

### 2-4. 他にも色々な設定を簡単に行える

他にも色々設定が簡単に行なえます。公式チュートリアルに色々載っているので参考になると思います。

* [聴衆 (自分の録画なし) としての参加](https://web.sdk.qcloud.com/trtc/webrtc/doc/en/tutorial-12-basic-live-video.html)
* [画面の共有](https://web.sdk.qcloud.com/trtc/webrtc/doc/en/tutorial-16-basic-screencast.html)
* [ミュートの切り替え](https://web.sdk.qcloud.com/trtc/webrtc/doc/en/tutorial-15-basic-dynamic-add-video.html)
* [ビデオ通話のキャプチャ](https://web.sdk.qcloud.com/trtc/webrtc/doc/en/tutorial-20-advanced-customized-capture-rendering.html)

## おわりに

今回試しに Tencent RTC Web SDK を触ってみたのですが、ビデオ通話アプリをバックエンド構築がほぼ不要で作れるので、通話機能だったりストリーミング機能を高速でサービスインのに非常に向いているなと感じました。

今回のサンプルアプリは、何も知らない状態から半日ぐらいで作ったのですが、設定もわかりやすく、殆どの時間をフロントエンドの実装に集中することができたので、開発体験としては結構良いんじゃないかと思います。
サンプル実装は↓で作ってみたので、興味がある人はそちらも見てみてください。

https://github.com/tomoasleep/hello-trtc