iPhone の Safari で、声が 2 回目から入らなかった。原因は「34 秒」だった

体調を記録するアプリを作っている。画面に質問が出て、声か文字で答えると、その日の記録が残る。声はブラウザの音声認識(Web Speech API)で文字にしている。

iPhone の Safari で、これが 2 問目から動かなくなった。1 問目は答えられる。2 問目でマイクを押すと、開始の音(ポロン)は鳴るのに、話しても何も入らない。画面には「聞き取れませんでした」とだけ出る。

見立てで直して、外し続けた

最初は「同じ認識器を使い回しているから、2 回目の start() を Safari が拒んでいる」と考えた。Playwright の WebKit で start() を 2 回呼ぶと、2 回目で InvalidStateError が出たからだ。押すたびに認識器を作り直す形に直した。

直らなかった。後で分かったが、この確かめ方が間違っていた。start() を同期で 2 回続けて呼んだだけで、1 回目がまだ開始中だった。開始中にもう一度呼べばエラーになるのは仕様どおりで、「終わった後にもう一度」は測っていなかった。測り直すと、WebKit は終わった認識器の再 start() を普通に受け付けた。

実機で測る前に入れた修正は、4 回とも外れた。PC の WebKit では、iPhone の音声まわりが再現できないからだ。

実機で、時刻つきで測った

見立てをやめて、使い方のページの中に自己診断を作った。マイクを押すたびに、何がいつ起きたかを 1 行ずつ並べる。

1 回目: start() 0.0秒 → 開いた 0.9秒 → 音を拾い始めた 1.4秒 → 聞こえた 2.4秒 → 終わった「はは」
2 回目: start() 0.0秒 → 開いた 0.0秒 → 音を拾い始めた 0.1秒 → 指を離した 2.5秒 → エラー aborted「No speech detected」

分かったことは 2 つあった。

マイクが実際には音を取り込まないまま、認識だけが動いているように見えた。

効かなかったこと

次の手はどれも効かなかった。

手で再読み込みすると 1 回目が動くので、「質問ごとにページを読み直す」形も入れた。これも実機では動かなかった。

効いていたのは時間だった

読み直さずに 1 分待って押すと、2 回目も普通に入った。読み直して動いていたのは、読み直したからではなく、その間に時間が経っていたからだった。

前の声が終わってから次に押すまでの秒数を記録して、並べた。

前の声から 結果
32.4 秒 失敗
33.2 秒 失敗
34.0 秒 成功
35.8 秒 成功

境目は 34 秒前後だった。失敗した回も「終わった認識」として数え直しになる。1 台の iPhone で測った値なので、機種や iOS の版で違うかもしれない。

iPhone はキーボードの音声入力に任せた

40 秒待たせれば動いた。でも、答えるたびに 40 秒待たされるのは使いものにならない。録音してサーバで文字にすれば待ちは消えるが、費用がかかり、声が端末の外に出る。

iPhone では、アプリのマイクをやめて、キーボードのマイク(OS の音声入力)で話してもらうことにした。こちらには待ち時間が無い。答えの欄に触れるとキーボードが開き、最初の数回だけマイクの場所を案内する。PC と Android は、今までどおり押している間だけ聞く。

残したもの

いちばん高くついたのは、PC 上の確認で「直った」と判断したことだった。実機で測ってから直す、を順番どおりにやればよかった。