iOS Safari の音声認識が不安定すぎて諦めかけたけど、Web Audio API で解決した話

「iOS Safari だけちゃんと動かない!」
Mac Safari、Mac Chrome、Windows Edge、Windows Chrome、Android Chrome、全部問題ないのに iOS Safari だけ動かない!色々な環境でテストしたんだよ、僕は!君だけ動かないんだよ!
これは、iOS Safari の仕様という人もいれば、制限という人もいるし、バグと言ってる人もいる。Apple は基本的に「アプリはストアでリリースすればいいじゃないですかー!」というスタンスなので、Web アプリに関してはちょっと冷めた目で見ているという情報は結構あるし、実際にそう感じることも多い。
でもストアはストアで登録して色々設定してその後もずっと色々更新してってやるのがめんどくさいし、Web アプリ作るの楽しいし、TypeScript も楽しいし SvelteKit も楽しいから、Web アプリで実現したいんだよ!
問題の核心
今回は、Web Speech API の一つである SpeechRecognition、つまり Web ブラウザ用の API で「音声を聞いて文字に変換しますよー」という API を使っているが、これが iOS Safari だけ「動いたり動かなかったり」するのだ!一番厄介なタイプのバグだ!
情報を探すと色々出てくる。「こうやって回避したらいい」とかは、とにかく全て試した。それでもなんか不安定で止まってしまうんだ。止まったときは、10 秒ほど待てばまた正常に動作するようになる。
ある程度の検証を続けてみて、ふと思ったことがあった。
「音声再生中に音声認識を開始するとだめなんじゃないか」
「音声認識中に、音声再生を開始してもだめなんじゃないか」
検証を続けていくうちに、おかしくなるのは上記のようなタイミングのように感じてきた。
AI さんからのアドバイス
このあたりの検証情報をもとに AI さんに聞いてみたら、「HTMLAudioElement 使わないで Web Audio API を使ったら安定するよ。完璧に動く保証はないけどね」と教えてくれた。
何!?完璧に直らないのかよっ!
でもやってみる価値はある。
実装してみた結果
実際にやってみたら、理想通り完璧に動作するようになった!
いや、自分が不具合を見つけられていないだけかもしれないが、思いつく限りの動作テスト、つまり、「音声再生中に音声認識開始!」とか「音声認識中に音声再生開始!」とか、色々やってみたけど問題なく動作するようになった。
実装のポイント
Web Audio API への変更
HTMLAudioElement から Web Audio API への変更は、AudioContext を使って音声データを AudioBuffer として読み込み、それを AudioBufferSourceNode で再生する方式に変更した。これにより、iOS Safari でも音声再生と音声認識の競合が発生しなくなった。
適切なウエイトの設定
あとは、音声の再生と認識を切り替えるときに少しばかりウエイトも入れてある。Web Audio API を使ったとしても、iPhone の場合は、音声再生への切り替えには 700 ms、音声録音への切り替えには 200 ms ほどウエイトを入れないとおかしくなることがあるようだ。
別件で、マイク入力が開始されるときと停止されるときに通知音が鳴るんだけど、この通知音が鳴っているタイミングで音声を再生すると音量が小さくなってしまう。だから必然的に音声認識を切ったあとはちょっとウエイトを入れて待たないといけなかったりもするんだよな。
iPhone 実機でのテストの大変さ
これも言っておかないといけない気がする。「実際の iPhone で開発中の Web アプリを動かして確認する」のはかなり大変。localhost アドレスで Mac の開発中の Web アプリに繋げられないし、マイクはマイクで HTTPS で繋がないと使えないし、とにかくやたらめったらめんどくさいんだ。
このあたりは、ngrok さんの力を借りることでなんとか解決できた。
試行錯誤の過程
実際、Web API で実現するのは諦めかけていた。クラウドの音声認識サービスを使うことも考えたし、サーバー側で認識させる方法として、たとえば OpenAI の音声認識モデル Whisper を使った「Whisper.cpp」を使うことも考えて色々と調べたが、最終的に Web API で実現できてよかった。
ただ、まだ不具合が残っている可能性はある。諦めないぞー!不具合を見つけたらぜひ連絡してほしい!
実際の成果物
今回の問題解決により、話す英語ゲーム「Talk」は iOS Safari でも安定して動作するようになりました。Web Speech API と Web Audio API を組み合わせて、音声認識を使った英語学習ゲームを作っています。まだまだゲーム要素を何も含められていませんが、ぜひ実際に遊んでみてください!