Skip to content

Instantly share code, notes, and snippets.

@balloob
Created February 27, 2026 15:56
Show Gist options
  • Select an option

  • Save balloob/652c3c1f06f24be6899ae49f04e33ba4 to your computer and use it in GitHub Desktop.

Select an option

Save balloob/652c3c1f06f24be6899ae49f04e33ba4 to your computer and use it in GitHub Desktop.
Media player volume control analysis for Home Assistant

Media player volume control analysis

Analysis of all integrations providing media player entities, focused on volume control behavior.

Base class behavior

The default async_volume_up/async_volume_down in MediaPlayerEntity (__init__.py:1036-1070):

  1. If the entity defines a sync volume_up/volume_down method, calls it via executor
  2. Otherwise, if volume_level is known and VOLUME_SET is supported, calls set_volume_level(volume_level +/- volume_step) where volume_step defaults to 0.1 (10%)

This means: if an integration supports VOLUME_SET and has accurate volume_level, the base class can handle stepping automatically. Overriding is only truly needed when:

  • The device has a native step command that should be preferred
  • The volume level might not be up-to-date (push devices with stale state)
  • A different step granularity is needed (though _attr_volume_step can handle this)

Integrations with BOTH VOLUME_SET and VOLUME_STEP that override volume_up/down

These are the key candidates for analysis. Could they drop their overrides and rely on the base class?

Use native device step commands (override provides real value)

These integrations call a device-specific step API rather than computing current + delta. The native command is preferred because it works even if volume_level is stale, and respects the device's own step granularity.

Integration Override calls Update model Could remove override?
smartthings Command.VOLUME_UP/DOWN via SmartThings API Push No - native command preferred
philips_js sendKey("VolumeUp"/"VolumeDown") Coordinator No - native key press
soundtouch device.volume_up()/volume_down() Polling Maybe - polling keeps volume_level fresh
control4 setIncrementVolume()/setDecrementVolume() Coordinator No - native command preferred
denon_rs232 receiver.volume_up()/volume_down() Push No - native serial command
vizio device.vol_up(num=step)/vol_down(num=step) + optimistic update Polling No - configurable device step + optimistic
apple_tv atv.audio.volume_up()/volume_down() Push No - native command, volume_level may be stale
volumio volumio.volume_up()/volume_down() Polling Maybe - polling keeps volume_level fresh
music_assistant player_command_volume_up()/volume_down() Push No - native command preferred
kodi kodi.volume_up()/volume_down() Conditional (websocket/poll) No - native command preferred
kef speaker.increase_volume()/decrease_volume() Polling Maybe - polling keeps volume_level fresh
samsungtv KEY_VOLUP/KEY_VOLDOWN IR keys Coordinator + UPnP No - IR key command, not absolute volume
russound_rio zone.volume_up()/volume_down() Push No - native command preferred
panasonic_viera Keys.VOLUME_UP/DOWN key commands Polling No - IR key command
braviatv client.volume_up()/volume_down() Coordinator No - native command preferred
hegel COMMANDS["volume_up"/"volume_down"] Push No - native command, push model
onkyo Volume(zone, UP/DOWN) Push No - native command, push model
openhome device.increase_volume()/decrease_volume() Polling Maybe - polling keeps volume_level fresh
lg_netcast LG_COMMAND.VOLUME_UP/DOWN remote commands Polling No - IR-style remote command
denonavr receiver.async_volume_up()/volume_down() Hybrid (telnet push + poll fallback) No - native command preferred
cambridge_audio client.volume_up()/volume_down() Push No - native command, push model
enigma2 SetVolumeOption.UP/DOWN enum commands Coordinator No - native step enum command
nad (RS232) main_volume("+")/("-") Polling No - native RS232 step command
pioneer telnet "VU"/"VD" commands Polling No - native telnet command
arcam_fmj state.inc_volume()/dec_volume() Push No - native command, push model
yamaha_musiccast musiccast.volume_up(zone)/volume_down(zone) Push + coordinator No - native command, push model
roon change_volume_raw(relative) or change_volume_percent(3) Push No - native relative volume API
androidtv ADB key events returning new volume level Polling No - ADB command returns updated level
webostv client.volume_up()/volume_down() Hybrid (poll + push) No - native command, VOLUME_SET is conditional

Compute current + delta using set_volume (override could potentially be removed)

These integrations read the current volume, adjust by a fixed delta, and call set_volume on the device (or HA's set_volume_level). The base class does essentially the same thing. They could be replaced by setting _attr_volume_step to the appropriate value.

Integration Override logic Step size Equivalent _attr_volume_step Update model Could remove override?
frontier_silicon get_volume(), then set_volume(vol +/- 1) 1 raw unit Depends on device max volume Polling Yes - set volume_step, use base class
group set_volume_level(level +/- 0.1) per child 0.1 (10%) 0.1 (already default!) Push Yes - base class does exactly this
monoprice set_volume(raw +/- 1), raw range 0-38 1/38 ~= 0.026 ~0.026 Polling Yes - set volume_step to 1/38
bluesound set_volume_level(level +/- 0.01) 0.01 (1%) 0.01 Coordinator Yes - set _attr_volume_step = 0.01
clementine set_volume(raw +/- 4), raw range 0-100 4/100 = 0.04 0.04 Polling Yes - set _attr_volume_step = 0.04
mpd setvol(current +/- 5), raw range 0-100 5/100 = 0.05 0.05 Polling Yes - set _attr_volume_step = 0.05
aquostv volume(raw +/- 2), raw range 0-60 2/60 ~= 0.033 ~0.033 Polling Yes - set volume_step to 2/60
songpal set_volume(vol +/- 1) on device 1 raw unit Depends on device range Push Probably - but push model means volume_level could be stale
ws66i set_volume(raw +/- 1), custom range 1 raw unit Depends on MAX_VOL Coordinator Yes - set appropriate volume_step
nad (TCP) set_volume(raw +/- 2*step), configurable Configurable User-configured Polling Yes - translate step to volume_step
demo _attr_volume_level +/- 0.1 directly 0.1 (10%) 0.1 (already default!) Push (simulated) Yes - base class does exactly this

Note on the "compute + set" group: These could all be simplified by:

  1. Setting _attr_volume_step to the appropriate fraction
  2. Removing the volume_up/volume_down overrides
  3. Letting the base class call set_volume_level(volume_level +/- volume_step)

The main risk is for push-based integrations (songpal) where volume_level might be briefly stale after a rapid sequence of volume steps. For polling-based integrations, this is less of a concern since the level was recently polled.


Integrations with ONLY VOLUME_STEP (no VOLUME_SET)

These MUST override volume_up/down because the base class fallback requires VOLUME_SET.

Integration Override calls Update model
epson projector.send_command(VOL_UP/VOL_DOWN) Polling
xbox SmartGlass volume(VolumeDirection.Up/Down) Coordinator
xiaomi_tv tv.volume_up()/volume_down() Assumed state
harman_kardon_avr avr.volume_up()/volume_down() Polling
hdmi_cec CEC KEY_VOLUME_UP/DOWN keypress Push
androidtv_remote "VOLUME_UP"/"VOLUME_DOWN" key commands Push
mediaroom stb.send_cmd("VolUp"/"VolDown") Push
roku roku.remote("volume_up"/"volume_down") Coordinator
lookin IR commands "volup"/"voldown" Push

Integrations that override volume_up/down WITHOUT declaring VOLUME_STEP

These have unreachable override code (since the service layer won't dispatch volume_up/down without VOLUME_STEP or VOLUME_SET supporting it, unless VOLUME_SET is declared too).

Integration Features Override calls Note
cmus SET only set_volume(raw +/- 5) Override reachable via VOLUME_SET base class path
sisyphus SET only set_speed(speed +/- 0.1) Override reachable via VOLUME_SET base class path
devialet SET only client.async_volume_up()/volume_down() Override reachable via VOLUME_SET base class path
denon SET only telnet "MVUP"/"MVDOWN" Override reachable via VOLUME_SET base class path
sonos SET only soco.volume +/- 2 Override reachable via VOLUME_SET base class path
dunehd NEITHER player.volume_up()/volume_down() Truly unreachable - no volume features declared

Note: For the ones with VOLUME_SET, the overrides ARE reachable because the service registration (lines 305-314 in __init__.py) allows volume_up/down when either VOLUME_SET or VOLUME_STEP is present. So these overrides do get called, they just don't advertise a step button in the UI.


Integrations with VOLUME_SET/VOLUME_STEP that DON'T override volume_up/down

These rely entirely on the base class default behavior.

With VOLUME_SET + VOLUME_STEP (base class handles stepping via set_volume_level)

Integration Update model Custom volume_step Notes
heos Coordinator + events No (default 0.1) Also has custom group volume up/down methods
squeezebox Coordinator Yes - configurable via options volume_step property divides config value by 100
unifiprotect Push No (default 0.1)
esphome Push No (default 0.1) Features are dynamic from device capabilities

With VOLUME_SET only (no VOLUME_STEP declared)

Integration Update model Custom volume_step Notes
cast Push No VOLUME_SET is conditional (not fixed volume)
jellyfin Coordinator No VOLUME_SET is conditional
spotify Coordinator No Premium users, non-restricted devices
tesla_fleet Coordinator Yes (dynamic) 1.0 / _volume_max / audio_volume_increment
teslemetry Coordinator/Stream Yes (1/31) Two entity variants: polling and streaming
tessie Coordinator Yes (1/31) Read-only entity, no volume control
snapcast Push No
itunes Polling No
yamaha Polling No Inverted dB-based volume scale
slimproto Push No
vlc Polling No
vlc_telnet Polling No Max volume is 500
bang_olufsen Hybrid (WS + poll) No
linkplay Polling (5s) No
plex Push No Volume stored locally (can't read from device)
dlna_dmr Hybrid (events + poll) No VOLUME_SET is conditional
anthemav Push Yes (0.01) 1% steps
lg_soundbar Push No
russound_rnet Polling No

Summary and recommendations

Could integrations with BOTH VOLUME_SET and VOLUME_STEP drop their custom overrides?

No, for most of them. ~30 integrations use native device step commands. These are preferable because:

  • They work even if volume_level is stale (important for push-based devices)
  • They respect the device's own step granularity and behavior
  • They avoid a read-modify-write cycle

Yes, for ~10 integrations that manually compute current + delta and call set_volume:

  • group and demo - override is identical to base class behavior (step = 0.1)
  • bluesound, clementine, mpd, monoprice, aquostv, frontier_silicon, ws66i, nad (TCP) - could set _attr_volume_step to their custom step size and remove the override

Integrations that should consider adding VOLUME_STEP to features

These override volume_up/down but don't declare VOLUME_STEP, meaning the UI won't show step buttons:

  • cmus, sisyphus, devialet, denon, sonos - all have working overrides that are callable but not properly advertised
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment