跳轉到

Audio

AudioManager.shared manages the audio route of a call: which microphone records, whether sound comes out of the speaker or the earpiece, and what happens when a headset is plugged in, another app plays audio, or a phone call interrupts. The SDK configures the audio session and starts the microphone and playback itself; the App only states its preferences and listens for changes.

Supported platforms

Device management is supported on iOS only. On macOS, AudioManager.inputDevice and AudioManager.outputDevice return nil, AudioManager.inputDevices and AudioManager.outputDevices return an empty array, AudioManager.isOtherAudioPlaying is false, and setting inputDevice does nothing.

Devices

An AudioDevice has three fields: id, the identifier iOS gives the port; name, the name iOS gives it, such as the name of a Bluetooth headset; and type, an AudioDevice.DeviceType. You cannot create an AudioDevice yourself: take one from AudioManager.inputDevices.

Device types

AudioDevice.DeviceType has six cases. Use it to show your own label or icon for a device:

Case Devices
builtInSpeaker The device's built-in speaker.
builtInReceiver The earpiece, the receiver you hold to your ear.
builtInMicrophone The device's built-in microphone.
wiredHeadset Wired headphones, and the microphone of a wired headset.
bluetooth Bluetooth audio: hands-free headsets, stereo headphones and Bluetooth LE audio.
unknown Everything else, including USB audio, CarPlay, AirPlay and HDMI.

Choose the microphone

AudioManager.inputDevices lists the microphones you can record from. AudioManager.inputDevice is the one recording now. To change it, set inputDevice to a device from inputDevices; set it to nil to go back to the system's choice.

The SDK only compares the id of the device you set: it looks for an available input with the same id, and when there is none, for example because the headset was unplugged, the system's choice is used. iOS treats the choice as a preference and may not follow it. The switch happens shortly after you set the property, so read inputDevice again in AudioManager.onDeviceUpdate (see Device changes) rather than right after setting it.

import RevueSDK

/// Record from a Bluetooth headset when one is connected, otherwise from the system default.
/// Pick the device from inputDevices: the SDK matches it by id. The switch happens shortly after,
/// so read inputDevice again in onDeviceUpdate rather than right after setting it.
@MainActor
func preferBluetoothMicrophone() {
    let audio = AudioManager.shared
    audio.inputDevice = audio.inputDevices.first { $0.type == .bluetooth }
}

Speaker or earpiece

AudioManager.outputDevices is the output on the current route: at most one entry, and empty when there is no route. It is not a list to choose from, because iOS has no list of selectable outputs. AudioManager.outputDevice is the same output on its own. Read them to show where sound is coming out.

To switch between the speaker and the earpiece, set AudioManager.isSpeakerOutputPreferred: true for the speaker, false for the earpiece. It defaults to true, so a call starts on the speaker unless you set it to false first. The SDK applies the preference again whenever the route changes or an interruption ends. The switch happens shortly after you set the property, so read outputDevices again in AudioManager.onDeviceUpdate (see Device changes) rather than right after setting it.

When an external device such as a wired or Bluetooth headset is connected and you are recording from it, the SDK does not change the output: sound stays on the headset whatever the preference. The preference is kept, and applies again once you record from a built-in microphone or unplug the headset.

import RevueSDK

/// Toggle between the speaker and the earpiece.
/// The switch happens shortly after, so read where sound now comes out in onDeviceUpdate, not here.
/// outputDevices is the current route, not a list to choose from: switch with isSpeakerOutputPreferred.
/// While the user records from a connected headset, the SDK leaves the route to the headset.
@MainActor
func toggleSpeakerOutput() {
    AudioManager.shared.isSpeakerOutputPreferred.toggle()
}

Microphone mute

AudioManager has no mute state. Whether the microphone is muted is microphoneTrack.isMuted, on LocalParticipant.microphoneTrack: mute it with mute() and unmute it with unmute(). A muted microphone keeps running and every recipient gets silence; see Mute a local track.

Device changes

AudioManager.onDeviceUpdate is called on the main thread every time the audio route changes: a headset plugged in or out, a Bluetooth device connecting or disconnecting, and the SDK switching between the speaker and the earpiece. It is passed the AudioManager itself; read inputDevices, inputDevice and outputDevices from it. It is a single closure, so setting it again replaces the previous one.

On Android the same event is deviceUpdates, a stream that several collectors can subscribe to at once, and each event carries the full list of devices instead of the AudioManager.

import RevueSDK

/// Refresh a device picker whenever the audio route changes: a headset plugged in or out, Bluetooth
/// connecting or disconnecting, or the SDK switching between speaker and earpiece.
/// onDeviceUpdate is called on the main thread with the AudioManager itself.
/// It holds one closure: setting it again replaces the previous one.
@MainActor
final class AudioDevicePicker {
    private(set) var inputs: [AudioDevice] = []
    private(set) var output: AudioDevice?

    init() {
        AudioManager.shared.onDeviceUpdate = { [weak self] manager in
            self?.inputs = manager.inputDevices
            self?.output = manager.outputDevices.first
        }
    }
}

Other apps' audio

AudioManager.isOtherAudioPlaying says whether another app is playing audio right now, such as a music app. AudioManager.onOtherAudioPlayingChange is called on the main thread when another app starts or stops playing, with the current value of isOtherAudioPlaying. Use them to decide how your own sounds mix with it, for example to lower your background music. The SDK only forwards the change and does not change its own behavior. Both are RevueSDK only.

On Android there is no onOtherAudioPlayingChange: read isOtherAudioPlaying when you need it.

Interruptions

A phone call, an alarm or Siri can take over the audio during a call. AudioManager.onInterruptionBegan is called when that starts and AudioManager.onInterruptionEnded when it ends, both on the main thread. Both are RevueSDK only.

When an interruption ends, the SDK reconfigures the audio session and restarts the microphone and playback by itself, whatever shouldResume says. shouldResume is for your own playback: true when the system suggests resuming, false when the source of the interruption still holds the audio, for example while a phone call is still going, and anything you play would likely not be heard.

On Android onInterruptionEnded has no shouldResume parameter.

import RevueSDK

/// Pause your own playback (a ringtone, background music) while a phone call or alarm takes over the audio,
/// and resume it only when the system says so. The SDK restores the call's audio by itself either way.
/// Both callbacks are called on the main thread.
@MainActor
final class BackgroundMusic {
    private(set) var isPlaying = false

    init() {
        let audio = AudioManager.shared
        audio.onInterruptionBegan = { [weak self] in
            self?.isPlaying = false
        }
        audio.onInterruptionEnded = { [weak self] shouldResume in
            if shouldResume { self?.isPlaying = true }
        }
    }
}

Format for your own audio

AudioManager.standardFormat is the format the SDK sends audio in: 32-bit float, 24,000 Hz, one channel, non-interleaved. When you push your own audio with AudioBufferCapturer (see Your own frames and audio), you can produce buffers in this format to save a conversion. Buffers in another format work too: the SDK converts them itself.