Contents

Handling audio interruptions

Observe audio session notifications to ensure that your app responds appropriately to interruptions.

Overview

Interruptions are a common part of the iOS, tvOS, visionOS, and watchOS user experiences. For example, consider the scenario of receiving a phone call while you’re watching a movie in the TV app on your iPhone. In this case, the movie’s audio fades out, playback pauses, and the sound of the call’s ringtone fades in. If you decline the call, control returns to the TV app, and playback begins again as the movie’s audio fades in.

At the center of this behavior is your app’s audio session. As interruptions begin and end, the audio session notifies any registered observers so they can take appropriate action. For example, AVPlayer monitors your app’s audio session and automatically pauses playback in response to interruption events. You can monitor these changes by key-value observing the player’s timeControlStatus property, and update your user interface as necessary when the player pauses and resumes playback.

Customize the interruption behavior

Most apps rely on the system’s default interruption behavior. However, AVAudioSession provides ways to customize the default behavior to better accommodate your app’s needs:

  • Recent iPad models provide a feature that mutes the built-in microphone at the hardware level when the user closes the device’s Smart Folio cover. If your app plays and records audio, you may want to continue playback even if the system mutes the microphone. You can disable the default interruption behavior by setting the overrideMutedMicrophoneInterruption option when configuring your audio session.

  • System alerts, such as receiving an incoming phone call, interrupt the active audio session. If you prefer that the system not interrupt your app’s audio session in these cases, you can indicate this preference by setting a value for the setPrefersNoInterruptionsFromSystemAlerts(_:) method.

Adopt the lifecycle notifications

In iOS 27, tvOS 27, visionOS 27, and watchOS 27 and later, AVAudioSession posts a set of life-cycle notifications that model interruptions as deactivation and resumption events rather than as begin and end signals. Adopt these notifications for new code because they represent the interrupted-versus-active state directly and don’t get out of sync when the system can’t deliver an end event.

The audio session posts three life-cycle notifications:

The system posts these notifications on the main queue.

Handle a deactivation

To respond to an interruption, observe didBecomeInactiveNotification and inspect the AVAudioSession.DeactivationContext in the user-information dictionary. The context’s source property indicates whether your app or the system initiated the deactivation. When the system initiates it, interruptionContext describes the interruption.

func observeDeactivations() async {
    let session = AVAudioSession.sharedInstance()
    for await notification in NotificationCenter.default.notifications(
        named: AVAudioSession.didBecomeInactiveNotification,
        object: session
    ) {
        guard let context = notification.userInfo?[AVAudioSession.deactivationContextKey]
                as? AVAudioSession.DeactivationContext else {
            continue
        }

        switch context.source {
        case .app:
            // Your app requested the deactivation.
            break
        case .system:
            // The system interrupted the session. Pause playback and update the UI.
            let reason = context.interruptionContext?.reason
            _ = reason
        @unknown default:
            break
        }
    }
}

Swift observers that adopt the NotificationCenter.MainActorMessage protocol’s type-safe message API receive an AVAudioSession.DidBecomeInactiveMessage value with an AVAudioSession.DeactivationResult enumeration that pattern-matches on the two possible outcomes:

let session = AVAudioSession.sharedInstance()

NotificationCenter.default.addObserver(of: session, for: .didBecomeInactive) { message in
    switch message.deactivationResult {
    case .appDeactivated:
        // Your app requested the deactivation.
        break
    case .systemInterruption(let context):
        // The system interrupted the session; inspect `context.reason`.
        _ = context.reason
    @unknown default:
        break
    }
}

Respond to a resumption recommendation

After a system interruption ends, or some cases when activation was rejected, AVAudioSession posts resumptionRecommendationNotification with an AVAudioSession.ResumptionContext value in the user-information dictionary. Read the context’s recommendation property and either play/resume playback or leave the session paused.

func observeResumption() async {
    let session = AVAudioSession.sharedInstance()
    for await notification in NotificationCenter.default.notifications(
        named: AVAudioSession.resumptionRecommendationNotification,
        object: session
    ) {
        guard let context = notification.userInfo?[AVAudioSession.resumptionContextKey]
                as? AVAudioSession.ResumptionContext else {
            continue
        }

        switch context.recommendation {
        case .shouldResume:
            // Re-activate the audio session, then resume playback.
            do {
                _ = try await session.activate()
            } catch {
                // Handle activation error.
            }
        case .shouldNotResume:
            // Leave playback paused.
            break
        @unknown default:
            break
        }
    }
}

Unlike the legacy interruption notification, resumptionRecommendationNotification isn’t tied to the interrupting app’s end-of-interruption signal, so your app doesn’t stay stuck in an interrupted state if that signal never arrives.

Observe the legacy interruption notification

When deploying to iOS 26, tvOS 26, visionOS 26, or watchOS 26 and earlier, observe interruptionNotification directly. In later releases, the system deprecates this notification, its user-information keys, and the associated enumerations in favor of life-cycle notifications.

func observeInterruptions() async {
    // Observe interruption notifications using async sequences.
    for await notification in NotificationCenter.default.notifications(
        named: AVAudioSession.interruptionNotification,
        object: AVAudioSession.sharedInstance()
    ) {
        handleInterruption(notification: notification)
    }
}

func handleInterruption(notification: Notification) {
    // To implement.
}

The posted Notification object contains a populated user-information dictionary that provides the details of the interruption. You determine the type of interruption by retrieving the AVAudioSession.InterruptionType value from the userInfo dictionary. The interruption type indicates whether the interruption is beginning or ending.

func handleInterruption(notification: Notification) {
    guard let userInfo = notification.userInfo,
        let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
        let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
            return
    }

    // Switch over the interruption type.
    switch type {

    case .began:
        // An interruption began. Update the UI as necessary.

    case .ended:
       // An interruption ended. Resume playback, if appropriate.

        guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
        let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
        if options.contains(.shouldResume) {
            // An interruption ended. Resume playback.
        } else {
            // An interruption ended. Don't resume playback.
        }

    default: ()
    }
}

If the interruption type is AVAudioSession.InterruptionType.ended, the userInfo dictionary contains an AVAudioSession.InterruptionOptions value, which you use to determine whether playback automatically resumes.

See Also

System audio