S氏がスマホをトランシーバー化したいというので頑張った話

S 氏へ
面白い相談を、ありがとう。

もらった要求をまとめる

- ツーリング時の通話に使いたい
- お金はかけたくない
- デバイスは追加したくない
- いったん切れても自動で再接続してほしい
- LINE の通話よりもラグを減らしたい
- 通信量は抑えたい

いくつか構成を伝えたが、今回はこんなイメージで作る

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113969/fc2a8036-dfbf-5a11-dd8f-6be254a6d77d.png)

# 気になる点はいくつかある

## テザリングで端末間の通信はできるのか

簡単にノートパソコン 2 台をスマホのテザリングで接続し
片方でローカルウェブサーバを起動し
片方でアクセスした

成功した
特にテザリングした中では端末間の送信は禁止されていなかった
場合によっては設定で変更かもしれない。。。

## ローカルだけで WebRTC 接続できるのか

普通にできるっぽい
インターネット越しに通話するとなるとサーバを使って使えそうな経路を調べることになるが、特にその設定をしなければローカルの経路情報だけが用意されて互いに通信するらしい

## SDP は QR コードにできるのか

試しに生成したローカルの経路情報付き SDP を QR コードにしてみた
ものすごい細かさなので読み取りが難しいが読むことはできた
あまりに使い勝手が悪ければ複数の QR コードに分ける手もあるので受け渡し方法は QR で問題なさそう

# ざっくりと設計

テザリングをするといっても
親機は SIM のない、モバイルルーターや使わなくなった端末かもしれない
そのため、インターネット接続がなくても動作するようにしたい
ネイティブアプリか PWA が考えられる

~~かなりどちらでもよかったが Flutter 触りたいのでネイティブアプリとして作る~~
と思ったがライブラリがビルドできないエラーを抱えているようだったので React(Next)を使った PWA に路線変更した

## 画面設計

ナビゲーションバーを使ってどの画面へも好きなタイミングで遷移できる

| 画面名      | 見えるもの           | できること         |
| ----------- | -------------------- | ------------------ |
| TOP         | マイクデバイス       | 使用デバイスの変更 |
|             | 接続状態             |                    |
|             | 接続中のセッション数 |                    |
| 招待        | リクエスト QR        |                    |
| QR 読み取り | カメラプレビュー     | QR の読み取り      |
|             | レスポンス QR        |                    |

※リクエストの QR の読み取り完了後は自動でレスポンスの QR を表示する
※レスポンスの QR の読み取り完了後は自動で TOP 画面へ遷移する

## ユーザの利用フロー

1. 全員、アプリをインストール
1. 全員、同じ Wi-Fi に接続
1. 全員、アプリを起動
1. 親が QR コードを表示する(招待画面)
1. 子が QR コードを読み取る(QR 読み取り画面）
1. 子が QR コードを表示する(QR 読み取り画面にて QR が自動で表示される）
1. 親が QR コードを読み取る(QR 読み取り画面）

これで通話できる
本来ならサーバ経由で送り合う SDP を QR コードで渡すことで通信を確立する
親 → 子だけでなく、レスポンスとなる子 → 親の情報伝達も必要になる
Flutter であれば親機をサーバにして子 → 親のステップを省けるはずだった
PWA ではサーバ機能を提供できないので断念

## 使えそうなライブラリ調査

### PWA

https://www.npmjs.com/package/next-pwa

### CSS フレームワーク

まぁ、、、とりあえず Bootstrap でいいか。。。
みんな使ってるし。。。

https://react-bootstrap.github.io/

一応ここで使用率はチェック

https://2021.stateofcss.com/en-US/technologies/css-frameworks

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113969/77e58e60-d6ad-a546-8d3c-59879a5515aa.png)

### WebRTC

そもそも Javascript で WebRTC が扱える
概念的には親子間で通信する際に互いに自分と相手の SDP を設定することで使えるらしい
設定するものとしては以下の表の通り
番号は今回の処理フローにおける設定順
QRで送っているのは1と3の情報

|     | LocalDescription | RemoteDescription |
| --- | ---------------- | ----------------- |
| 親  | 1 親の OfferSDP  | 4 子の AnswerSDP  |
| 子  | 3 子の AnswerSDP | 2 親の OfferSDP   |

https://caniuse.com/rtcpeerconnection

### QR コード生成

React 向けのもあるが
より人気のあるこっちにした
QR コード画像の URL を発行してくれる

https://github.com/soldair/node-qrcode

### QR コードリーダー

すごくいろいろあったが React 使うならこれでよさそう

https://github.com/react-qr-reader/react-qr-reader

こちらの記事が大変参考になりました

https://qiita.com/shora_kujira16/items/a9e3aa0b06525c00a7e0

### 状態管理

React 標準の context で作ってみる
Redux は Vuex を使ったことがあるのでなんとなく思想がわかったのと
実際に使うとなると学習コストがかかりそうだったので今回はパス

# 作ってみる

## Next.js プロジェクトの初期化

`npx create-next-app`コマンドは Nuxt ほどじゃないが気軽に使えてよかった
今回は Typescript と ESLint を有効にした
できれば CSS フレームワークやほかの Linter の導入も支援してほしい。。。

## ライブラリの導入

一旦使いそうなライブラリをすべて導入してビルドしてみる
Flutter で最初開発してて、中盤に差し掛かって導入したライブラリがビルドできないとかいう悲劇があったので、今回は最初にその可能性をつぶしておく

Bootstrap は特に問題なし、ついでに空のページとナビバーを追加してページ切り替えの動作確認

QR コードの読み取りは少しトラブル発生
React18 系に標準だと対応していなかった
Issue を参考に`npm i react-qr-reader --legacy-peer-deps`でインストールした
あまりいい対応ではないが動いたのでヨシ

QR コード生成は難なく成功

PWA は各ページのキャッシュ方法が分からなかったが
cacheOnFrontEndNav という設定があったので助かった

ひとまずどのライブラリも問題なく入って動かせた

## 音声通話を実現する

1 対 1 の通信確立にチャレンジする
この記事がものすごくわかりやすかった
https://lealog.hateblo.jp/entry/2019/04/22/095422

OfferSDPはQRにできたがAnswerSDPが若干容量オーバーでQRにできなかった
そもそもQRが細かすぎてスマホ同士で読める気がしないので
SDPを分割して複数のQRを一定間隔で表示することで情報を渡すことにした
QRの冒頭にインデックス情報をつけることで結合できるように工夫

意外な挙動をもう一つ確認した
AndroidはChromeでマイクを使用しているとChromeを通話アプリとして扱うらしい
このせいで音量が0にできなかった
さらにどのタブかは関係ないらしく、マイクを使っていないタブの音も通話扱いになっていた
今回は通話するためのアプリなのでいったん気にせず進めるが、音量を0にできないのでデバッグ中にハウリングが止まらない

## 複数人との通話を実現する

接続を複数持って管理できるようにする
これは単純でRTCPeerConnectionのインスタンスを複数持てるようにObjectに格納して保管するだけ

## デプロイ

誰でも使えるように公開する
Vercelを使ったことがなく、Nextに触れたこのタイミングで使ってみることにした
驚くほど簡単だった
もうHerokuは時代遅れなのかもしれない。。。