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.