TRTCを使ってWatermark付きの映像配信をしてみた感想。

# はじめに
これまで、Amazon Chime SDK, Twillio, Zoom SDK, Janus Gatewayなどを用いてビデオ会議アプリを開発してきました。より幅を広げようと思い、今回初めてTRTC(Tencent Real-Time Communication)を勉強しています。

[ひとつ前の投稿](https://qiita.com/wok/items/a24ace540dc1404c4f45)では、Typescript+ReactでHello world的なところまで作ってみて、感想を述べました。その後、ちょっとしたサービスを作成しようと思っていたのですが、TRTCにはいろいろと面白い機能がありそうなので、それについても引き続き記事にしていきたいと思います。

今回は、Advanced Tutrialに掲載されているwater mark機能を見ていきたいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365991/d776582d-6d09-688a-5cf1-fb56a66ed831.png)


# Watermarking
そもそも、Watermarkingとは何ぞや？という話ですがGoogleで調べるとこんな感じのものらしいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365991/082d242c-99cc-0898-8605-36e24258a2a5.png)

よく映像の隅っこににロゴみたいなものがついていることがあると思いますが、あれのことみたいですね。TRTCではこれを付けられるということのようです。

# 処理の内容
チュートリアルを読むと、どうやら

(1) HTMLCanvasElementにビデオとロゴを書き込む。
(2) そのHTMLCanvasElementからCaptureStreamでMediaStreamとVideoTrackを取得する。
(3) このVideoTrackを使ってLocalStreamを生成する

ということをやっているようです。createStreamメソッドを見てみるとわかりますが、LocalStreamがいろんなソースから生成できるようなインターフェースになっているため、できるというわけですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365991/9a72c108-eaf5-fab9-11e7-7f502c17ba42.png)

このようにMediaStream, VideoTrackを受け取るインタフェースになっていると柔軟な映像配信ができるのですが、これができないビデオ会議のSDKも結構あるのです。使いやすさとトレードオフみたいなところでポリシーが分かれているのだと思います。ちなみに、私は、いろいろと映像配信で遊びたいのでMediaStream, VideoTrackを受け取るインタフェースになっている方が好みです。

# 実装
今回は、[Pixabay](https://pixabay.com/ja/)からお借りした映像に、Pixabayのロゴを追加した映像を配信してみたいと思います。

## 前提 
[ひとつ前の投稿](https://qiita.com/wok/items/a24ace540dc1404c4f45)での、コードをベースに見ていくことにします。一度目を通しておいてください。

## コード概要
ひとつ前の投稿からの差分を説明していきます。（細かなところは省略します。後述するリポジトリでご確認ください。）

まず、カメラ映像を流すか、ビデオ＋ウォーターマークの映像を配信するかを選択できるようにメニューを追加します。
```ts
// メディアのタイプの定義
const MediaType = {
    camera: "camera",
    movie: "movie",
} as const
type MediaType = typeof MediaType[keyof typeof MediaType]
```
```tsx
// メニューコンポーネント
<div className="header-item-container">
    <div className="header-label">media type</div>
    <select onChange={(e) => { setMediaType(e.target.value as MediaType) }}> // ★ メディアのタイプが選択されたらStateにセット
        {Object.values(MediaType).map(x => {
            return (<option value={x} key={x}>{x}</option>)
        })}
    </select>
</div>
```

メニューで映像のタイプを選択したら配信する映像を切り替えるようにします。
```ts
const setMediaType = (val: MediaType) => {
    mediaTypeRef.current = val
    _setMediaType(mediaTypeRef.current)
}
useEffect(() => {
    publishLocalStream()
}, [mediaType]) // ★ メディアのタイプが選択されたら英ぞを切り替え
```

切り替え処理の実体は次のようになります。(★１)のところが送信する映像を切り替えるif文になります。（★２）は、Watermark付きの映像を作成している関数です。Canvasに映像とWatermarkを書き込んでいます。(★３)でCanvasからMediaStreamを取得してます。その後はこれを使ってLocalStreamを作成し、publishします。

```ts

    const publishLocalStream = async () => {
        if (!clientRef.current) {
            return
        }
        if (localStreamRef.current) {
            await clientRef.current.unpublish(localStreamRef.current);
            localStreamRef.current.stop()
            localStreamRef.current.close()
            localStreamRef.current = null
        }

        if (mediaTypeRef.current == "camera") { // ★1 このif分で送信映像を切り替える
            localStreamRef.current = TRTC.createStream({ userId: usernameRef.current, audio: true, video: true });
            const div = document.getElementById("local-video-container") as HTMLDivElement
            div.style.transform = ""

        } else {
            const canvas = document.createElement("canvas")
            const video = document.getElementById("input-video") as HTMLVideoElement
            canvas.width = 640
            canvas.height = 480
            const logo = document.getElementById("logo") as HTMLImageElement
            video.play()
            video.loop = true

            const drawCanvas = () => { // ★2 Canvasの中身を書き換える
                const ctx = canvas.getContext("2d")!
                ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
                ctx.drawImage(logo, canvas.width - logo.width - 10, canvas.height - logo.height - 10, logo.width, logo.height)
                if (mediaTypeRef.current == "movie") {
                    requestAnimationFrame(drawCanvas)
                }
            }
            drawCanvas()
            const canvasStream = canvas.captureStream();// ★3 MediaStreamを取得する
            localStreamRef.current = TRTC.createStream({ userId: usernameRef.current, videoSource: canvasStream.getVideoTracks()[0] });
            const div = document.getElementById("local-video-container") as HTMLDivElement
            div.style.transform = "scaleX(-1)"
        }
        await localStreamRef.current.initialize()
        localStreamRef.current.play('local-video-container');

        await clientRef.current.publish(localStreamRef.current);
    }
```
前回に処理を少し足しましたが、これでも１５０行強といったところでしょうか。

# デモ
このアプリはこんな感じで動きます。ちゃんと動いていますね。

https://user-images.githubusercontent.com/48346627/209360190-0b29b750-1243-4730-9918-7e7b0e1b19e1.mp4


# リポジトリ
リポジトリは次のところです。

https://github.com/w-okada/trtc-simple-demo-ts


# 使い方
リポジトリをクローンする。
```
$ git clone https://github.com/w-okada/trtc-simple-demo-ts.git  -b watermark
```

`src/const.ts`に情報を設定する。

そして、次のコマンドを実行してください。

```
$ npm install
$ npm run start
```
表示されたアドレスにブラウザでアクセスしてください。


# 感想
以上、TRTCをつかってwatermark付きの映像配信を行うことができました。前回も言った通り、かなり素直なつくりになっているので、他のSDKを使用した経験がある人はほとんど迷うことなく実装できると思います。