Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Apple Intelligence x Expoで作る音声メモアプリ

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Tenhou Tenhou
June 24, 2026
0

Apple Intelligence x Expoで作る音声メモアプリ

Avatar for Tenhou

Tenhou

June 24, 2026

Transcript

  1. BACKGROUND そもそも Audinote とは? 🖥 デスクトップ版(Electron) • USB の IC

    レコーダーを挿すと自動検出 • Gemini API で文字起こし・要約 • Markdown ファイルとして保存 • Obsidian のボルトに直接吐き出す → 📱 モバイル版(Expo / iOS) • アプリ内で録音 or ファイル選択 • Apple Intelligence でオンデバイス処 理 • 同じ Markdown 出力 • Obsidian アプリと共有 「ICレコーダーがなくてもスマホで同じことしたい」
  2. MOTIVATION なぜ Apple Intelligence を選んだか 🔒 プライバシー 音声がデバイス外に 出ない ⚡

    レイテンシ API呼び出し不要 録音後すぐ完了 💸 コスト 課金ゼロになる 🌐 オフライン 機内・地下でも動作 ただし iOS 26 + Apple Intelligence 対応機が必要 → 非対応端末は Gemini にフォールバック
  3. DESIGN Clean Architecture × 自動フォールバック 呼び出し側はバックエンドを意識しない Presentation MainScreen / HistoryScreen

    / SettingsScreen Application ProcessAudioUseCase Domain ITranscriptionService / ISummarizationService / IFilenameGenerationService Infrastructure Apple 実装 or Gemini 実装 ← DI コンテナが自動選択 起動時の自動選択 checkAvailability() ↓ iOS 26+ かつ AI 有効? YES → Apple 系サービス AppleTranscriptionService AppleSummarizationService NO → Gemini 系サービス GeminiTranscriptionService GeminiSummarizationService ↓ 同一インターフェース ProcessAudioUseCase
  4. IMPLEMENTATION ① Expo ローカルモジュールで Swift を呼ぶ modules/apple-intelligence/ に Swift コードを置くだけ

    Swift の AsyncFunction を宣言するだけで TypeScript の async 関数として呼べる ① Swift — AsyncFunction で宣言 AsyncFunction("summarize") { (text: String, prompt: String) async throws -> String in let session = LanguageModelSession() let res = try await session.respond(to: prompt) return res.content } Expo Bridge ←→ ② TypeScript — そのまま呼ぶだけ const M = requireNativeModule( 'AppleIntelligence') // 型補完が効く普通の async 関数 const summary = await M.summarize( transcription, userPrompt )
  5. IMPLEMENTATION ② SFSpeechRecognizer — オンデバイス文字起こし 音声ファイル → テキスト変換。データはデバイス外に出ない ✅ iOS

    標準、追加ライブラリ不要 ✅ オンデバイス認識でプライバシー保護 ⚠ 無音は空文字が正常終了で返る ⚠ シミュレータでは認識失敗しやすい let rec = SFSpeechRecognizer( locale: Locale(identifier: "ja-JP"))! let req = SFSpeechURLRecognitionRequest( url: audioURL) rec.recognitionTask(with: req) { result, _ in guard let r = result, r.isFinal else { return } let text = r.bestTranscription .formattedString // ⚠ 空文字が返ることがある guard !text.isEmpty else { throw .transcriptionEmpty } }
  6. IMPLEMENTATION ③ Foundation Models — iOS 26 Apple Intelligence のモデルをデバイス上で直接呼び出せる

    session.respond(to: prompt) ── これだけで推論完了 🧠 デバイス上で推論 クラウド通信ゼロ 💬 セッション管理 LanguageModelSession 🇯🇵 日本語対応 要約・ファイル名生成に活用 ⚙ Xcode 26 beta 必須 #if canImport で Xcode 16 でもビルド可 // 可用性チェック guard case .available = SystemLanguageModel .default.availability else { throw .unavailable } // セッション作成 & 推論 let session = LanguageModelSession( instructions: "音声要約AI") let res = try await session.respond(to: filledPrompt) return res.content
  7. TROUBLES ハマったこと 3 選 📤 Share Extension の通信 問題: URLスキームでアプリを起動

    しようとしたが Extension → アプリ 間 の通信ができない 解決: App Group の共有フォルダに ファイルを置いてポーリング監視 → 解決 ⏱ バックグラウンド処理 問題: 処理中に画面を離れると iOS がアプリを中断してしまう 解決: beginBackgroundTask() で 最大約3分を確保 Sendable 警告は Box クラスで解消 → 解決 🔇 SFSpeech の罠 問題: 無音・短い録音で空文字が 正常終了として返ってくる 解決: formattedString を trimming してから空チェック → カスタムエラーを throw → 解決
  8. SUMMARY まとめ Expo ローカルモジュール TypeScript から Swift のネイティブ API を型付きで呼べ

    る。 iOS 専用機能も React Native アプリに自然に組み込める 。 Foundation Models LanguageModelSession にプロンプトを投げるだけ。API は驚くほどシンプル。 ただし Xcode 26 beta 必須という現実的な制約がある。 SFSpeechRecognizer オンデバイス文字起こしはプライバシー・レイテンシ両 面で優れる。 空文字・ロケール・シミュレータ対応が地味にハマりど ころ。 フォールバック設計 インターフェースを統一して DI コンテナで差し替え。 起動時の自動判定で Apple 非対応端末でも Gemini が動く 。 ありがとうございました 🎙 → 📝