現在携わっている案件では、Electronを使用してデスクトップアプリを開発しています。
そんな案件で音声入力を実装したところ、思いもよらぬ罠にハマり1日以上費やしました(´•ω•`)
日本語で参考になる記事もほぼない状態でしたし、せっかくなので記事にしようと思います。
開発環境
- macOS Ventura 13.6.7
- Electron 28.2.0
- electron-builder 24.13.3
忙しい人のための結論(解決手順)
①src/main.js
など、メインプロセスを記述しているファイルにsystemPreferences.askForMediaAccess("microphone")
を実行する処理を追加(以下は一例)
ipcMain.handle("ask:mic-permission",async() => {
return await systemPreferences.askForMediaAccess("microphone");
});
②entitlements.mac.plist
(ない場合は作成が必要)に以下太字の箇所のコードを追加
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.device.audio-input</key>
<true/>
</dict>
</plist>
でマイクアクセス要求のポップアップが表示されるようになりました!
以下からはハマった経緯からこの結論に至るまでの道筋を書いてきます。
罠にハマるまで
Electronで音声入力を実装するといっても、API自体は実装されていたのであとはフロントのみ、という状態でした。
デザイン再現にあたり四苦八苦した箇所はあったものの、問題なく実装できたように見えました。
が、それはあくまでローカル環境での話でした。
後日、テスターの人から「音声入力動かないんだけど……」「アクセス許可のポップアップ出なくて……」という連絡があり、「そんなまさか〜」と思いつつビルドされたアプリを動かしました。
すると、なんということでしょう。
ローカル環境では出てたのに……。
そこから最優先で調査を開始しましたが、ローカル環境では再現しないので原因が全然わかりません。
そんな中、ふとElectron側で強制的にポップアップ出せないのかな、と公式ドキュメントを調べてみたところsystemPreferences.askForMediaAccess(mediaType)
というメソッドを発見します。
すぐさま「これだー!」と思い以下の処理を追加しました。
ipcMain.handle("ask:mic-permission",async() => {
return await systemPreferences.askForMediaAccess("microphone");
});
「これでいけるはず!」と、ビルドされたアプリを動かしてみると…。
あれ? 落ちたんだけど?
ここで、何か足りなかったのかと思い公式ドキュメントをもう一回見てみます。
この API を正しく活用するには、アプリの
Info.plist
ファイルにNSMicrophoneUsageDescription
とNSCameraUsageDescription
の文字列を設定する必要があります。
これかも!と思いInfo.plist
の元データとなるelectron-builder.yml
の中身を調べてみます。
~略~
extendInfo:
- NSMicrophoneUsageDescription: 音声入力に使用します
- NSCameraUsageDescription: 画像取り込みに使用します
~略~
設定してるぞ?(^ω^)
もう一回ビルドされたアプリを動かしてみます。
落ちます。
というわけで、systemPreferences.askForMediaAccess(mediaType)
メソッドでアプリが落ちる罠にハマってしまったのです。
救世主、現る
それからは「Electron 音声入力 動かない」「askForMediaAccess crush」などで何回もググったり、ChatGPTに聞いたりしてみたもののドンピシャな解決策には出会えず…。
そんな感じでハマりまくっている時、「そうだ、クラッシュレポートとか取れないんか?」と初心に帰って(?)「Mac クラッシュレポート」でググると…ついに救世主が現れました。
Macに標準搭載されているコンソールアプリです。
上記キャプチャはmacOS Monterey 12.7.5のものになります。
これは「開始」ボタンをポチッと押すと、上記画像のように各プロセスのモニタリングをしてくれるシロモノなのですが、これでアプリのクラッシュレポートが取得できるとのことで、すぐさまモニタリングを実行しました。
するとsystemPreferences.askForMediaAccess(mediaType)
メソッドで落ちる瞬間のログが取得でき、以下のメッセージが確認できました。
This app has crashed because it has a hardened runtime and attempted to access privacy-sensitive data without an entitlement indicating its intent to access this data. The app must have the 'com.apple.security.device.audio-input' entitlement.
Google翻訳にかけてみると、
このアプリは、強化されたランタイムを備えており、このデータにアクセスする意図を示す権限なしでプライバシーに敏感なデータにアクセスしようとしたためクラッシュしました。アプリには「com.apple.security.device.audio-input」権限が必要です。
つまるところ「プライバシーの中でも超デリケートなところだから、アクセス意図を示す権限(com.apple.security.device.audio-input)を追加しろ」ということらしい。でもどこに追加すればいいの?となりました。
なぜなら、ここまでググってきた中で得た数少ない情報を参考に、electron-builder.yml
にそれっぽいことを追加していたんですね。
~略~
extendInfo:
- NSMicrophoneUsageDescription: 音声入力に使用します
- NSCameraUsageDescription: 画像取り込みに使用します
- com.apple.security.device.audio-input: true ←これ
~略~
ここで結局振り出しに戻ったか…。と思いつつ、もしかしたら設定する場所が間違っているのかもしれない、と一縷の望みをかけてChatGPTくんに聞いてみました。
すると、「electron-builder.yml
じゃなくてentitlements.mac.plist
に追加するといいよ!(要約)」と言われました。
本当か?と思いつつ、electron-builder.yml
に追加していた記述を消して、entitlements.mac.plist
に以下太字の箇所のコードを追加してみました。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
〜略〜
<key>com.apple.security.device.audio-input</key>
<true/>
〜略〜
</dict>
</plist>
それからビルドされたアプリを半信半疑で動かしてみると…
出ました。マジびっくりした。
感動のフィナーレ
こうして、アプリ起動→音声入力ボタンを押す→マイクアクセス要求ポップアップ表示→「許可」をクリック→マイクで音声認識という動作が確認でき、罠から抜け出すことができたのでした。
1日以上あれこれ試行錯誤して、正直マジで詰んだと思った瞬間もあったので、あまりの感動にSlackへ喜びを投下してしまいました(チームの皆さんも反応してくれて嬉しかったです。優しい)。
あとがき
これまでmacOSでの開発はほとんど経験がないうえ、アプリといえばWebアプリの開発しか経験がなかったとはいえ、macOSのセキュリティが強いのはわかっていたつもりでした。しかし権限まわりでこんなにハマるとは思っていませんでした。macOSのセキュリティ、恐るべし。
そしていざというときに頼れるコンソール。これからも頼りにしたいと思います。
ChatGPTくんも色々教えてくれてありがとう!これからもよろしくね!
というわけで、ここまで読んでいただきありがとうございました。
同じ罠でハマってしまった方に届けば幸いです。
コメント