Skip to content

Add Home Assistant audio output sink selector#333

Open
booyasatoshi wants to merge 4 commits into
OHF-Voice:mainfrom
booyasatoshi:audio-output-sink-selector
Open

Add Home Assistant audio output sink selector#333
booyasatoshi wants to merge 4 commits into
OHF-Voice:mainfrom
booyasatoshi:audio-output-sink-selector

Conversation

@booyasatoshi

Copy link
Copy Markdown

Summary

This PR adds a Home Assistant device-page configuration select entity named Audio Output Sink for Linux Voice Assistant.

The new select exposes available PulseAudio/PipeWire output sinks discovered from the host/container audio environment, allowing users to choose which output sink the satellite should use directly from Home Assistant instead of manually editing AUDIO_OUTPUT_DEVICE every time.

This is intended to be generic audio-output selection support. It is not Sonos-specific. RAOP/Sonos sinks work because they are exposed as normal PulseAudio/PipeWire sinks.

Motivation

Linux Voice Assistant already supports selecting an output device at startup through AUDIO_OUTPUT_DEVICE, but there was no Home Assistant UI control for choosing or changing that output once the satellite was running.

This matters for systems where the available output sinks are exposed through PulseAudio/PipeWire, including local speakers, USB audio devices, Bluetooth sinks once connected by the OS, and RAOP/AirPlay sinks.

The goal is to make output selection visible and configurable from the Home Assistant device page while preserving existing startup configuration behavior.

Behavior

This PR supports two modes:

1. Environment override mode

If AUDIO_OUTPUT_DEVICE is set, it remains the hard startup override.

Example:

AUDIO_OUTPUT_DEVICE="pulse/raop_sink.Sonos-542A1BFB1574.local.192.168.86.226.7000"

On startup, the configured output device is still passed to mpv, but the underlying raw sink is also copied into ServerState.audio_output_sink.

That keeps the Home Assistant select state honest: the UI shows the actual active sink instead of incorrectly showing default.

2. UI-managed mode

If AUDIO_OUTPUT_DEVICE is not set, the selected sink is loaded from preferences.json as audio_output_sink.

When the user changes the Home Assistant select value:

  • The selected friendly label is mapped back to the raw PulseAudio/PipeWire sink name.
  • The raw sink is converted to an mpv-compatible device string.
  • Both the music player and TTS player are updated.
  • The raw sink name is persisted to preferences.
  • Selecting default clears the persisted sink and returns mpv to automatic/default output.

Implementation notes

linux_voice_assistant/audio_sinks.py

Adds helper functions for:

  • Listing PulseAudio/PipeWire sinks using pactl list short sinks
  • Converting raw sink names to mpv device strings
  • Converting raw sink names to Home Assistant-friendly labels
  • Building a label-to-sink map for the select entity

The helper keeps mpv-specific pulse/<sink> formatting at the mpv boundary. Internally, the selected sink is represented as the raw PulseAudio/PipeWire sink name or None.

linux_voice_assistant/entity.py

Adds AudioOutputSinkEntity, an ESPHome-style select entity exposed to Home Assistant.

The entity:

  • Uses entity category CONFIG
  • Uses icon mdi:speaker-wireless
  • Publishes available sink options
  • Publishes the currently selected sink label
  • Handles Home Assistant select commands

linux_voice_assistant/models.py

Adds:

  • Preferences.audio_output_sink
  • ServerState.audio_output_sink
  • ServerState.audio_output_sink_entity
  • ServerState.persist_audio_output_sink(...)

The persisted preference is used only when AUDIO_OUTPUT_DEVICE is not configured.

linux_voice_assistant/satellite.py

Creates and registers the new Audio Output Sink select entity.

Adds runtime sink switching logic that updates both:

  • music_player
  • tts_player

The select entity is added only once and is updated with the current sink options/state.

linux_voice_assistant/mpv_player.py

Adds MpvMediaPlayer.set_audio_device(...) so the satellite can change the player output device at runtime.

linux_voice_assistant/player/libmpv.py

Adds LibMpvPlayer.set_audio_device(...).

The implementation stops current playback before changing the mpv audio-device property. This is intentional because changing output sinks is a configuration action and should not try to preserve in-progress playback across devices.

If no device is provided, mpv is returned to auto.

linux_voice_assistant/__main__.py

Adds startup reconciliation logic so the effective output device is chosen from:

  1. AUDIO_OUTPUT_DEVICE, when provided
  2. preferences.audio_output_sink, when no environment override exists
  3. default/auto output, when neither is set

This preserves existing AUDIO_OUTPUT_DEVICE behavior while enabling UI-managed persistence.

Manual testing performed

Tested locally with a Linux Voice Assistant satellite using PulseAudio/PipeWire sinks.

Validated:

  • Stock Linux Voice Assistant worked before patching.
  • USB microphone input worked.
  • Host could play audio to the target sink through PulseAudio/PipeWire.
  • Docker container could output to the same sink using AUDIO_OUTPUT_DEVICE.
  • Home Assistant discovered the satellite normally.
  • Home Assistant showed the new Audio Output Sink select entity.
  • The dropdown contained default and detected sinks.
  • Runtime sink switching from Home Assistant worked.
  • Wake sounds and TTS output routed to the selected sink.
  • UI-selected sink persisted across container restart when AUDIO_OUTPUT_DEVICE was not set.
  • AUDIO_OUTPUT_DEVICE override still took precedence when set.
  • Home Assistant reflected the actual active sink when AUDIO_OUTPUT_DEVICE was set.
  • Selecting default cleared the persisted sink and returned mpv to automatic/default output.
  • Existing Home Assistant Assist pipeline behavior continued to work.

Files changed

  • linux_voice_assistant/audio_sinks.py
  • linux_voice_assistant/entity.py
  • linux_voice_assistant/models.py
  • linux_voice_assistant/satellite.py
  • linux_voice_assistant/mpv_player.py
  • linux_voice_assistant/player/libmpv.py
  • linux_voice_assistant/__main__.py

Notes

This PR intentionally keeps the feature scoped to output sink selection.

Bluetooth pairing/connection management is not included here. Bluetooth sinks can be selected if the host OS/PulseAudio/PipeWire layer has already exposed them as available sinks.

@Solarunit

Solarunit commented May 31, 2026

Copy link
Copy Markdown

Finding a correct sink could be a problem for new to linux users.
Personally I am using virtual sink (combine-sink pipewire feature) to avoid accidental switching to another output device if you have several.

@booyasatoshi

Copy link
Copy Markdown
Author

Finding a correct sink could be a problem for new to linux users. Personally I am using virtual sink (combine-sink pipewire feature) to avoid accidental switching to another output device if you have several.

Next PR will include a “test the sink” feature to play a sound over the selected sink to confirm its identity.

@Solarunit

Copy link
Copy Markdown

good idea!

@booyasatoshi

Copy link
Copy Markdown
Author

I am following up on this PR and wondering if there is a maintainer around that could approve the workflow? I have additional improvements to submit and I did not want to bloat this one with more changes.

@florian-asche florian-asche self-assigned this Jun 4, 2026
@florian-asche florian-asche added the enhancement New feature or request label Jun 4, 2026
@florian-asche

Copy link
Copy Markdown
Collaborator

I think this is a great MR. But with #350 we should have all input and output devices selectable in HA.

We also have some merge conflicts in this MR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants