この記事は iOS アプリ開発者向けです。7年前のアプリを近代化したあと、AdMob・カメラ・Text to Speech が audio session を奪い合って「起動直後に音楽がミュートされる」「読み上げ後に音楽が戻らない」が同時発生した。原因は iOS の Text to Speech が shared session を deactivate しない Apple 既知バグ(FB14444620)で、ワークアラウンド1行で解決。Build 19〜29 にわたる 11 回の試行錯誤を全部書く。
この記事でわかること
- AdMob・カメラ・音声読み上げが audio session を奪い合う構造と解決策
- Apple 既知バグ FB14444620(音声読み上げが shared session を deactivate しない)のワークアラウンド
- コードを直しても実機を再起動しないと確認できなかった話
前提:何を直したアプリか
わたしのほんやさんは2017年リリースの iOS 読書管理アプリ。v3.0.0 でコードベースを Xcode 26 / Swift 6 / SPM に全面移行したあと、v3.1.0 では Amazon アフィリエイト URL の現行化・AdMob 再有効化・Firebase Analytics 追加という3本立ての小規模リリースを予定していた。
実機で AdMob の動作確認をしようとしたところ、7年間ほぼ触っていなかった音声読み上げ・カメラまわりの問題が一気に露出した。結果として Build 19〜29 まで 11 サイクルを回すことになった。Build 19〜24 でカメラ・権限まわりの問題を片付け、Build 25〜29 が audio session との格闘だった。
audio session の三つ巴
症状は2つ同時に出た。
- 起動直後にシステムで再生中の音楽がミュートされる
- バーコードスキャンで本のタイトルを読み上げた後、音楽が再開しない
登場人物は3つ。全員が同じ AVAudioSession.sharedInstance() を取り合っている。
| コンポーネント | audio session への干渉 |
|---|---|
| GADMobileAds.start()(AdMob) | SDK 内部で .soloAmbient に上書きする(2020年代 AdMob の既知挙動) |
| AVCaptureSession(カメラ) | デフォルトで audio session を自動設定しようとする |
| AVSpeechSynthesizer(読み上げ) | speak() のたびに shared session をアクティブ化するが、終了後に deactivate しない |
試行錯誤の記録(Build 25〜29)
| Build | 試した対処 | 結果 |
|---|---|---|
| 25 | AVCaptureSession.automaticallyConfiguresApplicationAudioSession = false |
効果なし |
| 26 | 起動時に .ambient + audioSessionIsApplicationManaged = true を AdMob 前に設定 |
効果なし |
| 27 | didFinish で setActive(false, .notifyOthersOnDeactivation) |
効果なし |
| 28 | speak() 前に shared session を事前アクティブ化 |
逆効果(症状悪化) |
| 29 | synthesizer.usesApplicationAudioSession = false |
解決 |
原因:Apple 既知バグ FB14444620
Apple の公式ドキュメントは「setActive(false, .notifyOthersOnDeactivation) を呼べば他アプリが再開する」と書いている。これは正しい。しかし AVSpeechSynthesizer は 自分が session をアクティブ化した事実を覚えておらず、終了後に deactivate を呼ばない。公式ドキュメントに記載はないが、Apple Developer Forums で Apple エンジニアが認め、ワークアラウンドを提示している既知問題(FB14444620)だ。
解決策:synthesizer を shared session から切り離す
// synthesizer が shared audio session を触らないようにする
synthesizer.usesApplicationAudioSession = false
// 読み上げ終了後に他アプリへ「再開してよい」シグナルを送る
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer,
didFinish utterance: AVSpeechUtterance) {
try? AVAudioSession.sharedInstance().setActive(
false, options: .notifyOthersOnDeactivation)
}
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer,
didCancel utterance: AVSpeechUtterance) {
try? AVAudioSession.sharedInstance().setActive(
false, options: .notifyOthersOnDeactivation)
}
usesApplicationAudioSession = false で synthesizer が独自の private session を使うようになり、shared session への干渉が消える。
Build 25〜28 の対処はいずれも Claude Code が「これで直るはず」と判断してコミットしたものだが、実機では一度も音楽が戻らなかった——コードが間違っていたからだ。Build 29 のコードを入れた後も最初は同じ症状が続いたが、実機を再起動したところ正常に動いた。audio session の状態が OS に残留していたためで、再起動でクリアされた形だった。コードが正しくても、実機を再起動しないと確認できない——これが最終的な教訓だった。
その他の修正
カメラ許可後にプレビューが出ない
AVCaptureSession.startRunning() はメインスレッド禁止なので専用バックグラウンドキューで呼んでいたが、その後の setupCameraLayout()(プレビュー UIView の再配置)も同じキューで呼ばれていた。UIKit の操作はメインスレッドのみなので、これが原因でプレビューが出なかった。
sessionQueue.async {
self.captureSession.startRunning()
DispatchQueue.main.async {
self.setupCameraLayout() // UI 操作はメインスレッドに戻す
}
}
初回起動の連続ポップアップ
7年前にリリースしたころ、iOS のユーザー許諾ポップアップはほぼカメラ権限だけだった。その後 Apple は ATT(広告トラッキング許諾)・通知許諾・位置情報と、ユーザー同意を必要とするケースを年々追加してきた。古いアプリをそのまま動かし続けていると気づきにくいが、近代化して初めて実機を触ると4つのダイアログが一気に出る——これはコードのバグではなく、iOS のマイグレーションによる「許諾負債」の露出だ。
今回出た4つ:カメラアクセス(バーコードスキャン)、ATT(AdMob の広告トラッキング)、位置情報(近くのリアル図書館を検索する機能)、通知許諾——これが初回起動で連続して表示されていた。対処の構造:
- 通知許諾:2回目起動以降に延期(初回はアプリの価値を理解していない)
- 位置情報:図書館検索ボタンを押したタイミングでのみリクエスト
- ATT:
applicationDidBecomeActiveで呼ぶ(カメラ権限と衝突しないタイミング)
よくある質問
Q. Text to Speech で音楽が止まる問題、iOS のバージョンは関係しますか?
A. FB14444620 は iOS 17 以降で特に顕在化しています。AdMob SDK のバージョンとの組み合わせに依存する部分もあるため、まず usesApplicationAudioSession = false を試してください。
Q. usesApplicationAudioSession = false にすると何か副作用はありますか?
A. synthesizer が private session を使うため、他のオーディオとのカテゴリ調整が不要になります。読み上げ中に BGM を混在させたい場合は調整が必要ですが、「読み上げ後に音楽を戻す」だけが目的なら副作用はほぼありません。
Q. Build 29 のコードを適用しても症状が続く場合は?
A. 実機を完全に再起動してから確認してください。audio session の状態が OS に残留しており、アプリの再起動だけでは解消しないことがあります。
※本記事の手順・コードは執筆時点(2026年5月)の Xcode 26 / iOS 17 / AdMob SDK 11.x 環境で動作確認しています。ライブラリのバージョンが変わると挙動が変わる可能性があります。動かない場合はコメント欄でお知らせください。
まとめ
v3.1.0 の動作確認で実機を触ったら 11 回の TestFlight 提出になった。根本原因は Text to Speech が shared audio session を deactivate しない Apple 既知バグ(FB14444620)で、usesApplicationAudioSession = false の1行で解決した。7年前の設計をそのまま移植した結果、iOS 17 + AdMob の新しい組み合わせで初めて顕在化した形だった。
この記事が役に立ったら X(Twitter)でシェアしてもらえると喜びます。
このブログを書いた人のアプリ
本ブログの著者が作った iOS 読書管理アプリ わたしのほんやさん を App Store で公開しています。本棚をシンプルに管理したい方はぜひ。
関連記事
- 7年放置の iOS アプリを Claude Code で復活させて App Store に出した6日間【Xcode 26 / Swift 6 / v3.0.0】
- スマホのリモート操作で iOS アプリをビルド&TestFlight 配信する【SSH + build keychain・2026年版】
参考
- Apple Developer Forums: AVSpeechSynthesizer interrupts background audio (FB14444620)
- Apple Developer Documentation: usesApplicationAudioSession
- AdMob iOS Quick Start
※この記事は Claude Code を使った自動更新を試しています。
0 件のコメント:
コメントを投稿