ハイライト
iOS 17 と macOS 14 では、AirPods の自動切り替え、ステムを押してミュート、空間オーディオの統合コストがほぼゼロになります。メディア App は Now Playing に登録するだけで自動切り替えに参加でき、CallKit App はステムによるミュートを自動的に利用でき、AVPlayer と AudioQueue App では空間オーディオがすぐに使えます。
主要内容
AirPods は世界で最も広く使われているワイヤレスイヤフォンで、毎日何億もの人が音楽、動画、ビデオ会議に使っています。iOS 17 と macOS 14 は、AirPods 体験をさらにシームレスにする 3 つの重要な改善をもたらします。
自動切り替え
macOS 14 から AirPods の自動切り替えに対応します。ユーザーが iPhone で音楽を聴いているときに Mac で会議通知を受け取り、Mac のロックを解除すると、AirPods が自動的に接続され、確認バナーが表示され、iPhone 側の音楽は自動的に一時停止します。(01:33)
良い知らせは、すべての App Store App とサンドボックス外の App が追加対応なしで利用できることです。自動切り替えアルゴリズムは、Now Playing の登録状況と入力オーディオのアクティビティに基づいてルーティングを判断します。
ただし、体験を最適化するためのベストプラクティスがいくつかあります。(05:17)
- メディア/ストリーミング App は Now Playing に登録し、システムが適切に優先ルーティングできるようにする
- 会議/ゲーム App は Now Playing に登録しない
- 通知音には Audio Services API を使い、通知とメディアコンテンツを区別する
- 会議 App は通話中だけマイクを開き、終了後すぐに閉じる
- メディア App はユーザーが選んだデフォルトルートでオーディオを再生する
- ユーザーが一時停止したあとに無音を再生しない。必要な場合も 2 秒以内に抑える
ステムを押してミュート
iOS 17 と macOS 14 は、AirPods のステムを押してミュート/ミュート解除する操作に対応します。ユーザーが押すと、システムはマイク状態のバナーを表示し、通知音を再生します。(02:26)
iOS では、すべての CallKit App が追加コードなしでこの機能を自動的に利用できます。CallKit を使わないコミュニケーション App は、新しい AVAudioApplication API で対応します。(07:03)
macOS での動作は少し異なります。App はミュートジェスチャーの通知を受け取ったあと、アップリンク音声を自分でミュートする必要があります。(09:47)
空間オーディオ
Apple Music 登録者の 80% 以上が空間オーディオで聴いています。iOS 16 で導入されたパーソナライズされた空間オーディオは、iOS 17 でも対応範囲が広がります。(12:05)
macOS 14 は AVPlayer と AVSampleBufferAudioRenderer の空間オーディオに対応します。iOS と tvOS ではさらに AURemoteIO と AudioQueue にも対応します。後者 2 つには API インターフェイスはなく、Now Playing に登録した App で自動的に有効になり、ユーザーはコントロールセンターで設定できます。
詳細
iOS でステムによるミュートに対応する
import AVFAudio
import Combine
class AudioManager: ObservableObject {
@Published var isMuted = false
private var cancellables = Set<AnyCancellable>()
init() {
let center = NotificationCenter.default
center.publisher(for: AVAudioApplication.inputMuteStateChangeNotification)
.sink { [weak self] notification in
guard let self = self,
let userInfo = notification.userInfo,
let muteState = userInfo[AVAudioApplication.muteStateKey] as? Bool else {
return
}
self.isMuted = muteState
}
.store(in: &cancellables)
}
func toggleMute() {
let instance = AVAudioApplication.shared
instance.setInputMuted(!instance.isInputMuted)
}
}
キーポイント:
AVAudioApplicationは AVAudioSession ファミリーの新しいメンバーで、App レベルのオーディオ動作を管理するinputMuteStateChangeNotification通知はミュート状態が変化したときに発火するmuteStateKeyは通知のuserInfoから新しいミュート状態を取り出すsetInputMuted()は App 内でミュートを発生させるときに呼び出し、システム状態との同期を保つisInputMutedは現在のミュート状態を読み取る- 通知を購読することは、App がステムによるミュート機能にオプトインすることを意味する
macOS でステムによるミュートに対応する
import AVFAudio
import CoreAudio
class MacAudioManager {
let instance = AVAudioApplication.shared
func setupMuteHandler() {
instance.setInputMuteStateChangeHandler { isMuted in
// アップリンク音声をミュート
self.muteUplinkAudio(isMuted)
// UI 状態を更新
DispatchQueue.main.async {
self.updateMuteUI(isMuted)
}
// 正常に処理できたかを返す
return true
}
}
func muteUplinkAudio(_ isMuted: Bool) {
var propertyAddress = AudioObjectPropertyAddress(
mSelector: kAudioHardwarePropertyProcessInputMute,
mScope: kAudioObjectPropertyScopeInput,
mElement: kAudioObjectPropertyElementMain
)
var muteValue: UInt32 = isMuted ? 1 : 0
let size = UInt32(MemoryLayout.size(ofValue: muteValue))
AudioObjectSetPropertyData(
kAudioObjectSystemObject,
&propertyAddress,
0,
nil,
size,
&muteValue
)
}
func updateMuteUI(_ isMuted: Bool) {
// マイク状態インジケータを更新
}
}
キーポイント:
- macOS では
setInputMuteStateChangeHandlerが必須で、システムは通知だけを担当し、ミュートロジックは App が実装する - Handler は正常に処理できたかを示す
Boolを返し、不適切なミュート操作を拒否できる kAudioHardwarePropertyProcessInputMuteは CoreAudio の新しいプロパティで、有効にすると IO を動かしたままプロセスのすべての入力オーディオをミュートする- Handler は UI 更新に使わず、UI 更新は
inputMuteStateChangeNotification通知で処理する AudioObjectSetPropertyDataはシステムレベルのオーディオオブジェクトを操作するため、エラーコードを正しく処理する必要がある
Now Playing 登録
メディア App が Now Playing に登録することは、自動切り替えと空間オーディオに参加する鍵です。
import MediaPlayer
func setupNowPlaying() {
var nowPlayingInfo = [String: Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = "Song Title"
nowPlayingInfo[MPMediaItemPropertyArtist] = "Artist Name"
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 1.0
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.addTarget { _ in
// 再生を処理
return .success
}
commandCenter.pauseCommand.addTarget { _ in
// 一時停止を処理
return .success
}
}
キーポイント:
MPNowPlayingInfoCenterは現在再生中のコンテンツをシステムに登録する- 登録後、システムはユーザーが選択したデバイスへオーディオを正しくルーティングできる
- 空間オーディオは、Now Playing に登録した App の AudioQueue と AURemoteIO で自動的に有効になる
- 会議/ゲーム App は、自動切り替えアルゴリズムを妨げないよう Now Playing に登録しない
オーディオルート変更の監視
import AVFAudio
func setupRouteChangeMonitoring() {
NotificationCenter.default.addObserver(
self,
selector: #selector(handleRouteChange),
name: AVAudioSession.routeChangeNotification,
object: nil
)
}
@objc func handleRouteChange(_ notification: Notification) {
guard let userInfo = notification.userInfo,
let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
return
}
let session = AVAudioSession.sharedInstance()
let currentRoute = session.currentRoute
switch reason {
case .newDeviceAvailable:
// AirPods が接続された
let hasHeadphones = currentRoute.outputs.contains { port in
port.portType == .headphones || port.portType == .bluetoothA2DP
}
if hasHeadphones {
print("AirPods connected")
}
case .oldDeviceUnavailable:
// AirPods が切断された
print("AirPods disconnected")
case .categoryChange:
// オーディオカテゴリの変更
break
default:
break
}
}
キーポイント:
routeChangeNotificationはオーディオルートが変化したときに発火するAVAudioSessionRouteChangeReason列挙型は変更理由を示すcurrentRoute.outputsには現在のすべての出力ポートが含まれる.bluetoothA2DPと.headphonesで AirPods の接続状態を検出できる.oldDeviceUnavailableを正しく処理すると、AirPods 切断時に再生を一時停止できる
重要ポイント
1. スマート会議アシスタント
何を作るか: ビデオ会議 App に AirPods のステムによるミュートを統合し、UI でマイク状態をリアルタイム表示する。
なぜ価値があるか: AVAudioApplication によりミュート対応は数行のコードで済む一方、会議中の操作効率を大きく高められる。
始め方: inputMuteStateChangeNotification を購読し、@Published プロパティで SwiftUI のマイク状態インジケータを駆動し、ツールバーにミュートアイコンを表示する。
2. デバイス間のオーディオ状態同期
何を作るか: ポッドキャストまたは音楽 App で、AirPods が iPhone から Mac に切り替わったとき自動一時停止し、戻ってきたら再生位置を復元する。
なぜ価値があるか: 自動切り替えはシステム機能だが、App レベルの再生状態同期は開発者側で実装する必要がある。
始め方: routeChangeNotification を監視し、.oldDeviceUnavailable で現在の再生位置を保存し、.newDeviceAvailable で同じ AirPods か確認して再生を復元する。
3. 空間オーディオ対応ゲーム 何を作るか: プレイヤーの頭の向きに基づく空間オーディオをゲームに追加し、敵の位置を音で判断できるようにする。 なぜ価値があるか: 空間オーディオは AURemoteIO と AudioQueue App で自動的に有効になるため、マルチチャンネルコンテンツを提供するだけでよい。 始め方: Dolby Atmos など空間オーディオ対応の形式でゲーム効果音をエンコードし、AudioQueue で再生する。ヘッドトラッキングはシステムが自動処理する。
4. AirPods 接続を意識した UI
何を作るか: オーディオ App で AirPods の接続状態を検出し、接続時は空間オーディオのスイッチを表示し、切断時は最適な体験のためイヤフォン接続を促す。
なぜ価値があるか: オーディオデバイスによって体験は大きく異なるため、デバイスタイプに応じて UI を調整するとユーザー満足度を高められる。
始め方: AVAudioSession.sharedInstance().currentRoute.outputs を定期的に確認し、ポートタイプに応じてインターフェイス要素を動的に調整する。
関連セッション
- What’s new in AVFoundation — オーディオ処理の改善を含む AVFoundation フレームワークの新機能
- Support HDR images in your app — マルチメディアコンテンツ処理のベストプラクティス
- Explore AirPods features for your app — AirPods 機能をさらに統合するためのガイド
コメント
GitHub Issues · utterances