Skip to content
Document DB v12 - Improved Interceptors with Soft Delete Integration, AI protections, & Admin UI with Aspire Integration! How!?

Audio Output

The IAudioOutputDevices service tells you where the music is going — the built-in speaker, wired or USB headphones, a Bluetooth speaker, the car, HDMI or AirPlay — and raises an event when that route changes, such as when the user unplugs their headphones or a Bluetooth speaker connects.

It is registered by AddShinyMusic(), so resolve it from DI like any other service:

public class NowPlayingViewModel(IAudioOutputDevices outputs, IMusicPlayer player)
{
// ...
}

Current returns the route music is playing through, or null when the platform reports none:

var current = outputs.Current;
if (current != null)
Console.WriteLine($"Playing on {current.Name} ({current.Type})");
// e.g. "Playing on JBL Flip 5 (BluetoothA2dp)"

Each route is an AudioOutputDevice:

PropertyTypeDescription
IdstringStable platform identifier (Apple port UID / Android AudioDeviceInfo.Id)
NamestringHuman-friendly name, e.g. "JBL Flip 5" or "Speaker"
TypeAudioOutputTypeNormalized route type
IsCurrentboolWhether this is the route in use right now

AudioOutputType normalizes what each platform reports into one set of values:

Unknown, BuiltInSpeaker, BuiltInReceiver, WiredHeadphones, WiredHeadset, Bluetooth (hands-free HFP/SCO/LE), BluetoothA2dp (stereo — the normal music route), Usb, CarAudio, Hdmi, AirPlay.

Rather than switching on every AudioOutputType value, use the helpers in AudioOutputExtensions. Each has an overload for both AudioOutputDevice and AudioOutputType:

Methodtrue forUse it for
IsWired()WiredHeadphones, WiredHeadset, Usb”is something plugged in”
IsBluetooth()Bluetooth, BluetoothA2dpshowing a Bluetooth badge
IsBuiltIn()BuiltInSpeaker, BuiltInReceivernothing is plugged in or paired
IsHeadphones()wired or Bluetooth headphones/headsets”audio is private to the user”
IsExternalSystem()CarAudio, Hdmi, AirPlayplayback has left the device
var current = outputs.Current;
if (current?.IsBluetooth() == true)
ShowBluetoothIcon();
else if (current?.IsWired() == true)
ShowWiredIcon();
else
ShowSpeakerIcon();

Changed fires whenever the active output route changes. The argument is the new Current value, which may be null:

outputs.Changed += (sender, device) =>
{
// Classic behavior: the user yanked the headphones out and audio fell back
// to the loudspeaker — pause rather than blast the room.
if (device?.IsBuiltIn() == true)
MainThread.BeginInvokeOnMainThread(_player.Pause);
};

GetOutputs() returns the output routes the OS reports, with IsCurrent set on the active one:

foreach (var device in outputs.GetOutputs())
Console.WriteLine($"{device.Name}{device.Type}{(device.IsCurrent ? " (current)" : "")}");

What this contains differs by platform — see below. It is not a device picker: no platform here lets an app switch the route.

GetOutputs() returns every connected output from AudioManager.GetDevices(Outputs).

Android’s AudioDeviceInfo carries no “active” flag, so Current is a derived value: the connected outputs are ranked the way the platform’s own media routing policy does and the winner is reported.

PriorityRoute
1Bluetooth A2DP
2Bluetooth hands-free (SCO/LE)
3Wired headset
4Wired headphones
5USB
6Car audio (Automotive bus)
7HDMI
8Built-in speaker
9Earpiece

That’s a very good approximation for media playback — but it is derived, not a platform guarantee. Route changes come from an AudioDeviceCallback registered on the first Changed subscription.

Current comes straight from AVAudioSession.CurrentRoute — the OS reports the active route directly, so nothing is inferred.

The OS exposes only the active route’s ports; discovering every reachable AirPlay or Bluetooth destination is owned by the system route picker (AVRoutePickerView / Control Center). So GetOutputs() returns the current route — usually a single entry — rather than a full list of destinations.

A mic-equipped wired headset reports as WiredHeadphones, because Apple only surfaces the headset microphone on the input side. Use IsWired() or IsHeadphones() instead of testing for WiredHeadset directly.

Route changes come from the AVAudioSession route-change notification, observed on the first Changed subscription.