maui-speech-to-text について
CommunityToolkit.Maui を使う .NET MAUI 開発者向けの音声テキスト変換ガイドです。権限、認識中の制御、イベント処理の注意点や、プラットフォームごとの問題とサービス化の方法を扱います。
主な機能
- 01開始前にマイクと音声の権限を確認
- 02タイムアウトとキャンセルトークンを設定
- 03イベントハンドラーを確実に解除
- 04部分結果でリアルタイム文字起こしを表示
- 05音声認識をサービスとしてラップ
活用シーン
- 01権限説明の不足による iOS クラッシュの回避
- 02Android のマイク権限拒否への対応
- 03バッテリー消費とイベント重複処理の防止
- 04MAUI UI でのリアルタイム文字起こし表示
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ davidortinau/ maui-skills --skill maui-speech-to-textインストール後、Claude Code で/maui-speech-to-textを入力して読み込まれたか確認します。
同梱ファイルreferences/speech-to-text-api.md
詳細
音声テキスト変換 — 注意点とベストプラクティス
サービスの完全な実装、型、UI 統合パターンについては、references/speech-to-text-api.md を参照してください。
重要:権限の処理
音声認識を開始する前に、必ず権限をリクエストしてください。マイクと音声認識の両方の権限が必要です。
// ❌ Starting recognition without checking permissions
var result = await _speechService.StartListeningAsync();
// ✅ Always check permissions first
if (!await _speechService.RequestPermissionsAsync())
return; // Gracefully handle denial
var result = await _speechService.StartListeningAsync(cancellationToken);
⚠️ iOS では両方の権限説明が必要です
Info.plist に NSSpeechRecognitionUsageDescription または NSMicrophoneUsageDescription のどちらかが欠けていると、適切に失敗するのではなく、実行時クラッシュが発生します。
⚠️ Android:権限の再リクエスト
Android では、ユーザーが RECORD_AUDIO 権限を2回拒否すると、OS はプロンプトを表示しなくなります。ユーザーを手動で設定画面に誘導する必要があります。
よくある間違い
❌ タイムアウトなし — 無期限のリスニング
バッテリーを消耗する無期限のリスニングセッションを防ぐため、必ずタイムアウトを設定してください。
// ❌ No timeout — listens forever if no speech detected
await _speechToText.StartListenAsync(options, CancellationToken.None);
// ✅ Use a combined timeout + user cancellation token
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(
userCancellationToken, timeoutCts.Token);
await _speechToText.StartListenAsync(options, combinedCts.Token);
❌ イベントハンドラーの登録解除忘れ
イベント購読を解除しないと、重複処理やメモリリークが発生します。
// ❌ Subscribe without unsubscribe
_speechToText.RecognitionResultUpdated += OnRecognitionResultUpdated;
// ✅ Always unsubscribe in finally block
try
{
_speechToText.RecognitionResultUpdated += OnRecognitionResultUpdated;
// ... listen ...
}
finally
{
_speechToText.RecognitionResultUpdated -= OnRecognitionResultUpdated;
}
❌ CancellationTokenSource の破棄忘れ
// ❌ Leaked CTS
_currentCts = new CancellationTokenSource();
// ✅ Dispose in finally
try { /* ... */ }
finally
{
_currentCts?.Dispose();
_currentCts = null;
}
プラットフォームごとの注意点
| プラットフォーム | 注意点 |
|---|---|
| iOS | plist キーのどちらかが欠けている → 実行時クラッシュ |
| Android | ユーザーが権限を2回拒否 → OS がプロンプトを表示しなくなる。設定画面への誘導が必要 |
| すべて | タイムアウトなし → 無期限のリスニングによるバッテリー消耗 |
| すべて | リスニング中に StartListeningAsync を呼び出す → 例外ではなくエラーが返される |
アーキテクチャに関するヒント
-
ISpeechToTextをサービスでラップする — ViewModel でSpeechToText.Defaultを直接使用しないでください。テスト容易性と状態管理のため、ISpeechRecognitionServiceでラップします。 -
UX のために部分的な結果を使用する — ライブ文字起こしのフィードバックを提供するため、
PartialResultReceivedを購読します。ユーザーは話すと同時に言葉が表示されることを期待しています。 -
連続リスニングは遅延を挟んだループにする — 会話モードでは、短い遅延(
Task.Delay(100))を挟みながらStartListeningAsyncをループします。 -
二重起動を防ぐ — 開始前に状態を確認します。
if (State == SpeechRecognitionState.Listening) return new SpeechRecognitionResultDto { Success = false, ErrorMessage = "Already listening" }; -
自然言語の出力 — CommunityToolkit.Maui は、生の音素ではなく、正規化され句読点が付与されたテキストを返します。基本的な用途では後処理は不要です。
-
UI に依存しないサービス —
ISpeechRecognitionServiceパターンは、XAML/MVVM、C# Markup、MauiReactor のいずれでも同じように機能します。3つのパターンすべてについては、references/speech-to-text-api.mdを参照してください。
チェックリスト
-
CommunityToolkit.MauiNuGet をインストール済み(最新バージョンを確認) -
MauiProgram.csでUseMauiCommunityToolkit()を呼び出している - DI 経由で
ISpeechToTextをシングルトンとして登録している - iOS:Info.plist に
NSSpeechRecognitionUsageDescriptionとNSMicrophoneUsageDescriptionの両方がある - Android:AndroidManifest.xml に
RECORD_AUDIO権限がある -
StartListeningAsyncを呼び出す前に毎回権限を確認している - タイムアウトを設定している(最大60秒を推奨)
-
finallyブロックでイベントハンドラーの登録を解除している - 使用後に
CancellationTokenSourceを破棄している