[Swift]他のアプリに音声を取られたときに自動で再生再開されるようにしておこう

音をとられたら取り返そう
2023.04.19

はじめに

CX事業本部の中安です。まいどです。

前記事では「シンプルなオーディオプレイヤーを作ってみよう」という記事の続きとして、 「イヤホン(ヘッドホン)が抜けて音声が外に漏れる事故がないように、接続状態をきちんと監視しよう」という記事にしたためました。

今回は、さらにオーディオプレイヤーアプリに見つかった "ある欠陥" への対策となります。 それはイヤホンで音楽を聞いているときに、PayPayなどで支払いをすると音が止まってしまうことでした。

アプリをバックグラウンド再生可能にしていると、他のアプリが音声を再生したりするとセッションが取られてしまいます。 また、音声の中断理由としては電話の入電なども考えられます。

その度に毎回アプリに戻って音声を再生し直すという、ユーザにとってなんとも面倒臭いアプリになってしまうので、 その対策を入れていきましょう。

イベントを取る

実件方法は、前回と同じように AVFoundationをインポートし、AVAudioSession.interruptionNotificationNotificationCenter 経由で監視してやります。

実装例を以下のような感じです。

// ビューコントローラであれば viewDidLoad。その他クラスであれば init() とかに書く
NotificationCenter.default.addObserver(
    self,
    selector: #selector(didAudioSessionInterruption(_:)),
    name: AVAudioSession.interruptionNotification,
    object: nil
)

セレクタとして定義したメソッドも実装します。

@objc private func didAudioSessionInterruption(_ notification: Notification) {
    // 後述します
)

今定義した didAudioSessionInterruption(_:) は、音声再生の中断が「始まった時」も「終わった時」も同じ箇所が呼ばれます。 そのため、それを判別するためにはイベント通知の中身を見る必要があります。

@objc private func didAudioSessionInterruption(_ notification: Notification) {
    guard
        let userInfo = notification.userInfo,
        let interruptionTypeRawValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
        let interruptionType = AVAudioSession.InterruptionType(rawValue: interruptionTypeRawValue)
    else {
        return
    }
    :

上記ソースコードのように IntegerLiteralTypeという列挙型のオブジェクトを userInfo から取得します。

そして、それをswitch文でケース分けしてやります。

switch interruptionType {
// 音声の中断が始まった時
case .began:

// 音声の中断が終わった時 
case .ended:

@unknown default:
     fatalError()
}

音声中断後の挙動を考える

今回は、PayPayアプリなどを使って支払いするときに「ペイペイっ」と音が鳴ってしまうことで、音声の再生が中断されてしまうことを想定しています。

「ペイペイっ」と鳴った後に自動的に音声再生が再開されるために、以下のように実装をします。

イヤホンが抜かれた時は、userInfoから AVAudioSessionRouteDescriptionというオブジェクトを抜き出し、 その中から先ほど同様にAVAudioSessionPortDescriptionを取得することができます。

switch interruptionType {
case .began:
    // 割愛
case .ended:
    var shouldResume = false
    if let interruptionOptionRawValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
        let interruptionOptions = AVAudioSession.InterruptionOptions(rawValue: interruptionOptionRawValue)
        shouldResume = interruptionOptions.contains(.shouldResume)
    }

    if shouldResume {
        player.unpause()
    }
@unknown default:
    fatalError()
}

「音声再開の必要があるかどうか」(shouldResume)という情報の有無を確認するのですが、 InterruptionOptionsOptionSetなので、containsメソッドで調べてあげる必要があります。

こうすることによって、他のアプリで中断されたあとも自動的に音声が再生されることになります。

おわりに

AVFoundationを使用した音声を扱うアプリであれば、音声の中断とその後に関するユーザのケアも必要になってくると思いますので、 ぜひご参考になさってください。

それではまたー