Amazon Chime SDK JSをローカルHTMLで雑に動かす

技術検証で Amazon Chime SDK を使い出したが、
APIも多く学び始めすぐ、0から動かすのに手間取った記憶がある。

そこで、Amazon Chime SDK に入門し公式サンプルプロジェクトを動かした後、
0から動かしてみるのに楽な最小構成を考えてみた。

### 大枠こんな感じ
- MeetingSession 起動に必須パラメーターは AWS CLI で作る
- ローカルサーバーとHTMLファイルだけで通話クライアントを起動する

### 何が美味しい？
ローカルサーバーで動くので各種クライアントAPIを試すのに便利

## 事前準備
- `AmazonChimeFullAccess`許可済み AWS IAM + AWS CLI
- [この公式サンプル](https://github.com/aws/amazon-chime-sdk-js/tree/master/demos/singlejs)を参考に Amazon Chime SDK JS のminファイルを出力する
- 適当なローカルサーバーを用意する（[http-server](https://www.npmjs.com/package/http-server)等）

## 使うHTML一枚
```html
<html lang="ja">

<head>
    <script src="./scripts/amazon-chime-sdk.min.js"></script>
    <style>
        .main {
            display: flex;
            align-items: flex-start;
        }
        #video-tile {
            width:80%;
            display: flex;
            flex-wrap: wrap;
        }
        #video-tile video {
            width: 46%;
            height: auto;
            margin: 2%;
        }
    </style>
</head>

<body>
    <div class="main">
        <form action="/" method="get" id="meeting-params">
            <div>
                <label for="">Meeting JSON</label>
                <br>
                <textarea name="meeting" id="" cols="200" rows="16">
{
    "Meeting": {
        "MeetingId": "EXAMPLE",
        "MediaPlacement": {
            "AudioHostUrl": "",
            "AudioFallbackUrl": "",
            "ScreenDataUrl": "",
            "ScreenSharingUrl": "",
            "ScreenViewingUrl": "",
            "SignalingUrl": "",
            "TurnControlUrl": ""
        },
        "MediaRegion": ""
    }
}</textarea>
            </div>
            <div>
                <label for="">Attendee JSON</label>
                <br>
                <textarea name="attendee" id="" cols="200" rows="8">
{
    "Attendee": {
        "ExternalUserId": "EXAMPLE",
        "AttendeeId": "",
        "JoinToken": ""
    }
}</textarea>
            </div>
            <div>
                <button>Start Chime</button>
            </div>
        </form>

        <div id="video-tile">
            <video id="video-tile-self"></video>
        </div>
        <audio id="audio-view"></audio>
    </div>

</body>

<script>
    const params = (new URL(document.location)).searchParams;
    const params_meeting = params.get('meeting');
    const params_attendee = params.get('attendee');

    (async () => {
        if (!params_meeting) {
            return;
        }else{
            const p = document.getElementById('meeting-params');
            p.hidden = true;
        }

        const meeting = JSON.parse(params_meeting);
        const attendee = JSON.parse(params_attendee);

        const logger = new ChimeSDK.ConsoleLogger('ChimeMeetingLogs', ChimeSDK.LogLevel.ERROR);
        const deviceController = new ChimeSDK.DefaultDeviceController(logger);
        const configuration = new ChimeSDK.MeetingSessionConfiguration(meeting, attendee);
        const meetingSession = new ChimeSDK.DefaultMeetingSession(configuration, logger, deviceController);

        try {
            const audioInputs = await meetingSession.audioVideo.listAudioInputDevices();
            const audioOutputs = await meetingSession.audioVideo.listAudioOutputDevices();
            const videoInputs = await meetingSession.audioVideo.listVideoInputDevices();
            await meetingSession.audioVideo.chooseAudioInputDevice(audioInputs[0].deviceId);
            await meetingSession.audioVideo.chooseAudioOutputDevice(audioOutputs[0].deviceId);
            await meetingSession.audioVideo.chooseVideoInputDevice(videoInputs[0].deviceId);
        } catch (err) {
            // handle error - unable to acquire audio device perhaps due to permissions blocking
        }
        const audioOutputElement = document.getElementById('audio-view');
        meetingSession.audioVideo.bindAudioElement(audioOutputElement);

        const videoElementSelf = document.getElementById('video-tile-self');
        const videoElementTile = document.getElementById('video-tile');

        const observer = {
            videoTileDidUpdate: tileState => {
                if (!tileState.boundAttendeeId) {
                    return;
                }
                if (tileState.localTile){
                    meetingSession.audioVideo.bindVideoElement(tileState.tileId, videoElementSelf);
                }else{
                    if(!document.getElementById(tileState.tileId)){
                        const node = document.createElement("video");
                        node.id = tileState.tileId;
                        videoElementTile.appendChild(node);
                    }
                    const videoElementNew = document.getElementById(tileState.tileId);
                    meetingSession.audioVideo.bindVideoElement(tileState.tileId, videoElementNew);
                }
            },
            videoTileWasRemoved: tileId => {
                const videoElementRemoved = document.getElementById(tileId);
                videoElementRemoved.remove();
            }
        };

        meetingSession.audioVideo.addObserver(observer);
        meetingSession.audioVideo.startLocalVideoTile();
        meetingSession.audioVideo.start();
    })();
</script>

</html>
```

## 利用手順
0. ビルドした Amazon Chime SDK JS min ファイルを上記 HTML に読ませる
0. AWS CLI 経由で Chime ミーティングを作成する
	- `$ aws chime create-meeting`
	- `$ aws chime create-attendee --meeting-id ($ aws chime create-meetingの返り値の MeetingId) --external-user-id (適当な参加者名）`
0. ローカルサーバーを起動し、2で得たJSONを貼り付けてセッションスタート

## このローカル起動クライアントでも、複数人参加ができる
接続先のセッションは「Amazon Chime メディア・サービス」がよしなに用意してくれるので、ローカルにあるクライアント同士でも1on1ができる

### 複数人参加の手順
- meeting JSON パラメーターを共有し入力
- attendee JSON パラーメーターは参加者の数だけ個別に作り入力