Skip to content

pyodio API reference

Everything listed here is importable from the top-level pyodio package. Units follow MPRIS: positions and durations are integers in microseconds, volumes are floats between 0.0 and 1.0. Methods marked async are coroutines. This page documents pyodio 0.2.

pyodio.connect(base_url="http://localhost:8018", session=None, *, keepalive=30, request_timeout=10.0)

Creates an OdioHub and connects it. The result can be awaited (odio = await pyodio.connect(...), close it yourself) or used as an async context manager (closed on exit). Arguments are those of OdioHub.

NameValueDescription
DEFAULT_BASE_URL"http://localhost:8018"Default API URL
ADDED"added"Change kind, an entity appeared
UPDATED"updated"Change kind, an entity or domain changed
REMOVED"removed"Change kind, an entity disappeared
POSITION"position"Change kind, a player position beacon
TRACKLIST"tracklist"Change kind, a player’s tracklist changed
DISCOVERED"discovered"Change kind, a Bluetooth device appeared
PROGRESS"progress"Change kind, an upgrade run progressed
__version__Package version
OdioHub(base_url="http://localhost:8018", session=None, *, keepalive=30, request_timeout=10.0)

Stateful client kept in sync over SSE. See Live state with OdioHub.

ParameterDescription
base_urlAPI base URL
sessionaiohttp.ClientSession to reuse, never closed by pyodio
keepaliveSSE keepalive interval in seconds, 10 to 120. The stream times out after keepalive + 15 s of silence.
request_timeoutREST request timeout in seconds
MemberDescription
async connect() -> OdioHubFetch the snapshot, then start the event stream. Raises and closes the hub if the snapshot fails.
async start() -> OdioHubStart the event stream without waiting for a snapshot
async close()Stop the stream and close the client
async withconnect() on entry, close() on exit
server: ServerInfoNode identity and backends. Raises OdioError before the first sync.
backends: BackendsShortcut for server.backends
connected: boolWhether the event stream is up
client: OdioClientThe underlying REST client
players: PlayersMPRIS players
audio: AudioAudio server, clients, outputs
services: Servicessystemd units
bluetooth: BluetoothBluetooth adapter and devices
power: PowerPower capabilities and actions
upgrade: UpgradeUpgrade status and actions
on_event(listener) -> unsubscribelistener(event: OdioEvent) for every SSE event
on_connection_change(listener) -> unsubscribelistener(connected: bool) on stream up and down

Read-only mapping of Player by bus name.

MemberDescription
find(name) -> Player | NoneLook up by bus name, app name or identity, case-insensitive for the last two
playing: list[Player]Players currently playing
on_change(listener) -> unsubscribelistener(change, player), changes ADDED, UPDATED, REMOVED, POSITION, TRACKLIST
mapping protocolplayers[bus_name], in, len(), keys(), values(), items()
PropertyTypeDescription
bus_namestrMPRIS bus name, org.mpris.MediaPlayer2.<app>
app_namestrBus name without the MPRIS prefix
identitystrName the player reports
availableboolFalse once the player is removed
playback_statusstrA PlaybackStatus value
is_playingboolAvailable and playing
title, artist, album, art_urlstr | NoneFrom metadata
metadatadict[str, str]All metadata, flattened to strings
durationint | NoneTrack length, µs
positionint | NonePosition in µs, extrapolated while playing, clamped to duration
volumefloat | NonePlayer volume
shufflebool | NoneShuffle state
loop_statusstr | NoneA LoopStatus value
capabilitiesPlayerCapabilitiesWhat the player supports
cover_urlstrCover proxy URL, changes with the track and artwork
tracklist_supportedboolImplements MPRIS TrackList
tracklistTracklistState | NoneRaw tracklist
trackslist[Track]Tracklist entries, empty if none
current_trackTrack | NoneEntry matching the current track id
can_edit_tracksboolTracks can be added and removed
statePlayerStateRaw model
MethodDescription
async play(), pause(), play_pause(), stop(), next(), previous()Transport
async seek(offset: int)Relative seek, µs, negative to go back
async set_position(position: int, track_id: str | None = None)Absolute position, µs. track_id defaults to the current track.
async set_volume(volume: float)Player volume
async set_loop(loop: str)"None", "Track" or "Playlist"
async set_shuffle(shuffle: bool)Shuffle on or off
async refresh_tracklist() -> TracklistStateFetch and cache the tracklist
async go_to(track: Track | str)Play a tracklist entry
async add_track(uri: str, *, after_track: Track | str | None = None, set_as_current: bool = False)Add an absolute URI, appended by default
async remove_track(track: Track | str)Remove a tracklist entry
MemberDescription
kind: str"pulseaudio" or "pipewire"
volume: float | NoneMaster volume, follows the default output
muted: bool | NoneMaster mute, follows the default output
default_output: AudioOutput | NoneThe default sink
clients: Mapping[str, AudioClient]Application streams by name
outputs: Mapping[str, AudioOutput]Output devices by sink name
server: AudioServerState | NoneRaw master state from the last snapshot
async set_volume(volume: float)Master volume. ValueError out of [0, 1].
async toggle_mute()Toggle master mute
async set_muted(muted: bool)Reach a master mute state, toggling only if needed
on_change(listener) -> unsubscribelistener(change, client_or_output), changes ADDED, UPDATED, REMOVED
MemberDescription
name: strStream name, the key in audio.clients
app: strApplication name
volume: float, muted: boolStream volume and mute
corked: boolPaused by the application
is_remote: boolStream comes from another host than the node
state: AudioClientStateRaw model
async set_volume(volume), toggle_mute(), set_muted(muted)Stream volume and mute
MemberDescription
name: strSink name, the key in audio.outputs
description: strHuman-readable name
volume: float, muted: boolOutput volume and mute
is_default: boolDefault output
state: AudioOutputStateRaw model
async set_volume(volume), toggle_mute(), set_muted(muted)Output volume and mute
async make_default()Make it the default output

Read-only mapping of Service by scope/name.

MemberDescription
find(name, scope=None) -> Service | NoneLook up by unit name, optionally in one scope
on_change(listener) -> unsubscribelistener(change, service), changes ADDED, UPDATED, REMOVED
MemberDescription
name: str, scope: strUnit name and ServiceScope value
running: bool, enabled: boolUnit state
description: strUnit description
state: ServiceStateRaw model
async start(), stop(), restart(), enable(), disable()Lifecycle, user scope only
MemberDescription
powered: bool, scanning: bool, pairing_active: boolAdapter state, False when unknown
devices: Mapping[str, BluetoothDevice]Known and discovered devices by MAC
connected_devices: list[BluetoothDevice]Connected devices
state: BluetoothState | NoneRaw adapter model
async power_up(), power_down()Adapter power
async pairing_mode()Make the node discoverable for pairing
async scan(), scan_stop()Device discovery
async connect(address), disconnect(address)Connect or disconnect a device by MAC
on_change(listener) -> unsubscribelistener(UPDATED, bluetooth) on adapter changes, listener(DISCOVERED | UPDATED, device) on devices
MemberDescription
address: str, name: strMAC address and name
paired: bool, connected: boolDevice state
state: BluetoothDeviceStateRaw model, with bonded and trusted
async connect(), disconnect()Connect or disconnect this device
MemberDescription
can_reboot: bool, can_power_off: boolCapabilities, False when unknown
capabilities: PowerCapabilities | NoneRaw model
async reboot(), power_off()Power actions
MemberDescription
available: boolAn upgrade is available
current_version: str, latest_version: strVersions, empty when unknown
in_progress: boolA run is in progress
progress_percent: int | NoneRun progress
status: UpgradeStatus | NoneRaw model, with the current run in status.run
async check()Look for a new version
async start()Start the upgrade, OdioApiError 409 if already running
on_change(listener) -> unsubscribelistener(UPDATED | PROGRESS, upgrade)
OdioClient(base_url="http://localhost:8018", session=None, *, request_timeout=10.0)

Stateless REST client. The full method list, with the request each one sends, is on the low-level client page.

MemberDescription
base_url: strBase URL without trailing slash
session: aiohttp.ClientSessionSession in use, created on first access if none was given
async close()Close the session if the client created it
async withCloses on exit

Dataclass with type: str (event type, such as player.updated) and data (the JSON-decoded payload, or the raw string if it is not JSON).

pyodio.stream_events(client, *, types=None, backends=None, exclude=None, keepalive=30) -> AsyncIterator[OdioEvent]

Yields events from GET /events until the connection drops, without reconnecting. Raises OdioTimeoutError or OdioConnectionError.

EventStream(client, *, types=None, backends=None, exclude=None, keepalive=30, on_connected=None)
MemberDescription
async start()Start the background task, no-op if running
async stop()Stop it, and mark the stream disconnected
connected: boolWhether the stream is up
add_event_listener(listener) -> unsubscribelistener(event: OdioEvent)
add_connection_listener(listener) -> unsubscribelistener(connected: bool)

on_connected is a coroutine function awaited on every connection, before events are dispatched.

Dataclasses returned by OdioClient and held in each entity’s state. All parse with Model.from_dict(data), ignoring unknown keys and defaulting missing ones. Datetimes are datetime objects, None when absent.

hostname, os_platform, os_version, api_sw, api_version: str, backends: Backends.

bluetooth, mpris, power, pulseaudio, systemd, upgrade, zeroconf: bool.

reboot, power_off: bool.

FieldType
bus_name, identitystr
playback_statusstr
loop_statusstr | None
shufflebool | None
volume, ratefloat | None
positionint | None, µs as last reported
position_updated_atdatetime | None
metadatadict[str, str]
capabilitiesPlayerCapabilities
tracklist_supportedbool

Properties: app_name, is_playing, title, artist, album, art_url, track_id, duration.

can_play, can_pause, can_go_next, can_go_previous, can_seek, can_control: bool.

TracklistState: can_edit_tracks: bool, tracks: list[Track].

Track: track_id: str (MPRIS object path), metadata: dict[str, str], properties title, artist, album, art_url, duration.

AudioSnapshot: kind: str, clients: list[AudioClientState], outputs: list[AudioOutputState].

AudioServerState: kind, default_sink: str, volume: float, muted: bool.

id: int, name, app: str, muted: bool, volume: float, corked: bool, backend, binary, user, host: str, props: dict[str, str].

id: int, name, description, nick: str, muted: bool, volume: float, state: str, default: bool, driver, active_port: str, is_network: bool, props: dict[str, str].

name, scope, active_state: str, running, enabled, exists: bool, description, url: str. Property key, "scope/name".

BluetoothState: powered, discoverable, pairable, pairing_active: bool, pairing_until: datetime | None, scanning: bool, known_devices: list[BluetoothDeviceState].

BluetoothDeviceState: address, name: str, paired, bonded, trusted, connected: bool.

UpgradeStatus: current, latest: str, upgrade_available: bool, checked_at: datetime | None, extra: dict, run: UpgradeRunState, can_check, can_upgrade: bool.

UpgradeRunState: state: str (an UpgradeRunStateValue), origin: str, percent: int | None, step: str | None, started_at, finished_at: datetime | None. Property running.

String enums, comparable with plain strings.

EnumValues
PlaybackStatusPLAYING = "Playing", PAUSED = "Paused", STOPPED = "Stopped"
LoopStatusNONE = "None", TRACK = "Track", PLAYLIST = "Playlist"
ServiceScopeSYSTEM = "system", USER = "user"
UpgradeRunStateValueIDLE = "idle", RUNNING = "running", FAILED = "failed"
ExceptionBaseRaised when
OdioErrorExceptionBase class, also raised when reading OdioHub.server before the first sync
OdioConnectionErrorOdioErrorThe server can’t be reached, or the stream is lost
OdioTimeoutErrorOdioConnectionErrorA request times out, or the stream misses its keepalive
OdioApiErrorOdioErrorThe server answers with an HTTP error. Attributes status: int, message: str.

See error handling.