ovos-media¶
Maturity — Proof-of-concept ⬤◯◯◯◯
The ovos-media daemon is unfinished. It is the upcoming media-playback service for OVOS, still being refactored, and not enabled by default. Today, stock installs play media through the legacy audio backend, the ovos-ocp-audio-plugin ("old audio service") inside ovos-audio, which is deprecated but still shipped. Switching to ovos-media is opt-in and the player-as-skill coupling remains (the in-process GUI surface was dropped in 2.0.0a1; any GUI is an external bus client). Treat this page as the target architecture, for exploration only. Rated by repository health, not version.
OVOS has two media-playback backends sharing the same OCP search framework (pipeline + skills + extractors):
| Legacy (current default) | ovos-media (upcoming) | |
|---|---|---|
| Package | ovos-ocp-audio-plugin in ovos-audio |
ovos-media (standalone daemon) |
| Status | deprecated, still shipped & on by default | opt-in refactor, not default |
| Playback | one bundled audio backend | per-request audio/video/web media plugins (opm.media.*) |
| Extras | none | MPRIS, per-session state, multiple players |
| Config | enable_old_audioservice: true (default) |
enable_old_audioservice: false + run ovos-media |
The OCP pipeline and stream extractors are unaffected by which backend you use. Only the playback layer differs. (OCP skills are a separate, longer-term change.)
In a nutshell
ovos-media is the planned future replacement for how OVOS plays music, podcasts and videos. Today, stock installs still use the older audio backend. ovos-media is an opt-in, work-in-progress rewrite meant to handle audio, video and web playback more cleanly and to support several players at once. If you are not deliberately trying it out, you are not using it yet. This page describes where things are heading. See the OCP Pipeline for how playback requests are recognized, or the Glossary for terms.
ovos-media is the standalone audio/video daemon for OpenVoiceOS. It is the upcoming
replacement for the legacy audio service, and it provides a more capable and modular media player
built on the OpenVoiceOS Common Play (OCP) framework.
In plain terms: the old audio service could only play one kind of stream through a thin wrapper. ovos-media is a proper media daemon. It has separate audio/video/web players you pick per request. It supports MPRIS, so your phone's media controls work, and it keeps per-session state so multiple devices can each play their own thing.
📐 Formal specification
Media playback is specified by two architecture documents:
OVOS-OCP-1 — OVOS Common Playback
defines the per-session virtual media player, the single logical
player every "play / pause / next / louder / stop" command targets, plus
the MPRIS bridge to and from the host OS. Meanwhile,
OVOS-AUDIO-1 — Audio Output Service
defines the output service that actually renders queued audio. OCP-1
fixes the observable control surface. How a URI becomes bytes on a
speaker is a backend concern. ovos-media is the implementation moving
toward that contract. For the full set see the
spec index.
To use ovos-media you need to disable the old audio service and enable the OCP pipeline in ovos-core:
{
"intents": {
"pipeline": [
"ovos-converse-pipeline-plugin",
"ovos-ocp-pipeline-plugin-high",
"...",
"ovos-common-query-pipeline-plugin",
"ovos-ocp-pipeline-plugin-medium",
"...",
"ovos-ocp-pipeline-plugin-low",
"ovos-fallback-pipeline-plugin-low"
]
}
}
Architecture¶
OCP (OVOS Common Play) splits into a search/match layer and a playback layer. The search
layer, made up of the ocp-pipeline, OCP skills (media catalogs), and stream extractors,
is shared regardless of which playback layer you run. The playback layer has two implementations
that currently run in parallel: the legacy "old audio service"
(ovos-ocp-audio-plugin inside ovos-audio) and the standalone ovos-media
daemon described here, which is the target:
flowchart TD
Core["ovos-core intent pipeline"] --> Pipeline["ocp-pipeline-plugin"]
Pipeline -->|"enable_old_audioservice: true (default)"| Legacy["ovos-ocp-audio-plugin (in ovos-audio)"]
Pipeline -->|"enable_old_audioservice: false"| Media["ovos-media (standalone daemon)"]
Media --> PSM["Player state machine (OCPMediaPlayer)"]
Media --> MPRIS
Media --> Backends["Media backend plugins"]
Media --> GUI["GUI (external bus clients since 2.0.0a1)"]
Diagram: the intent pipeline routes to either the legacy ovos-ocp-audio-plugin or the standalone ovos-media daemon, which drives the player state machine, MPRIS, backend plugins, and the GUI.
OCP Pipeline Plugin¶
The OCP pipeline plugin (OCPPipelineMatcher, import name ocp_pipeline) is the search/match
layer: it classifies a media request, dispatches search to OCP skills over the bus, picks the
best-scoring result, and routes it to the active player. It does NOT handle playback. Full
detail (classification, per-session player state, pipeline configuration, OCP skills, bus
messages) has moved to its own page: ovos-media OCP Pipeline Plugin.
ovos-media Player¶
Entry point: ovos-media (runs ovos_media.__main__:main). The installed console script
parses --help and --version and exits before starting the daemon or touching any socket.
Key modules:
ovos_media/player/__init__.py:OCPMediaPlayer, the player state machine (playlist, track history, playback/media/loop state)ovos_media/catalog/catalog.py:MediaCatalog(aliasedOCPMediaCatalogfor back-compat, instantiated as the player'sself.media) — a plain object with no bus subscriptions of its own, holding only the liked-songs store and search-results playlist-
ovos_media/skill.py:OCPVoiceSkill, anOVOSCommonPlaybackSkillsubclass constructed separately from the player and given a reference to its catalog — the actual voice front-end, registering the now-playing intents and the liked-songs@ocp_search -
ovos_media/media_backends/:AudioService,VideoService,WebService. Each manages typed backend plugins -
ovos_media/player/__init__.py: pushes player state only throughovos.common_play.*bus broadcasts (2.0.0a1 dropped the in-processGUIInterfacealong with the oldovos_media/gui.py/OCPGUIInterface); any GUI,ovos-control-panel(formerly ovos-webui) included, is an outboard bus client -
ovos_media/mpris.py: MPRIS integration
Available Media Backend Plugins¶
Media backends are typed: audio players register on the opm.media.audio entry-point group,
video players on opm.media.video, and web players on opm.media.web. They are configured under
media.audio_players / media.video_players / media.web_players (see the config example below).
The table below lists the pip package to install. Each package registers one entry point per
type it supports, named ovos-media-<type>-plugin-<name> (e.g. the ovos-media-plugin-vlc
package registers ovos-media-audio-plugin-vlc and ovos-media-video-plugin-vlc). You configure
the player by its entry-point name.
| Package | Types | Description |
|---|---|---|
ovos-media-plugin-vlc |
audio, video | VLC instance |
ovos-media-plugin-mplayer |
audio, video | mplayer |
ovos-media-plugin-cli |
audio | Generic CLI-command player, minimal/default audio fallback |
ovos-media-plugin-spotify |
audio | Spotify Connect |
ovos-media-plugin-chromecast |
audio, video | Cast to a Chromecast device |
ovos-media-plugin-qt5 |
audio, video, web | Hand off to the GUI player. Legacy, depends on the deprecated ovos-shell (see GUI status) |
ovos-media-plugin-mass |
audio | Hands playback off to a Music Assistant server. next()/previous() on the legacy AudioBackend interface delegate to the Music Assistant queue API (see Known Coupling Issues below) |
ovos-media-plugin-mpris |
audio, video | Not yet published (no public repo or PyPI package). Drives an external MPRIS player (e.g. an already-running desktop media app) instead of playing the stream itself |
music-assistant-plugin-manager
(pip install --pre music-assistant-plugin-manager, since every release is a
prerelease) loads out-of-tree Music Assistant providers
into a Music Assistant server, via its music-assistant-community wrapper command. It is
independent of ovos-media and only concerns the Music Assistant server itself, not OCP.
Stream Extractor Plugins¶
OCP supports stream extractor plugins (opm.ocp.extractor entry-point group. The older
ovos.ocp.extractor group is a deprecated alias) that transform non-playable URIs into playable
streams before handing them to the media backend:
-
ovos-ocp-youtube-plugin: extracts audio stream from YouTube URLs -
ovos-ocp-m3u-plugin: parses M3U playlists -
ovos-ocp-rss-plugin: parses podcast RSS feeds
Media Intents¶
These are the utterances the OCP pipeline plugin
described above actually matches, at whichever confidence tier (-high / -medium / -low)
it's configured for. Before the regular intent stage, OCP handles these utterances (taking into
account current player state):
-
"play {query}": always available -
"previous": requires media loaded -
"next": requires media loaded -
"pause": requires media loaded -
"play"/"resume": requires media loaded -
"stop": requires media loaded -
"I like that song": not matchable today —like_song.intentandplay_favorites.intentare commented out of the pipeline's intent list, so their files are never loaded, on either stack. Theocp:like_song/ocp:play_favoriteshandlers exist and the bus messages work; only the voice route is disabled.
Now-Playing Intents¶
OCPVoiceSkill (the built-in ovos-media skill described under ovos-media
Player) registers five regular padatious intents (en-us) about the
currently playing track: WhatSong, WhatArtist, WhatAlbum, ShuffleOn, and ShuffleOff.
WhatSong and WhatArtist answer from the player's global status
(ovos.common_play.status), the same read-only state every session gets back from a status
query — so these two intents answer on any session, not just the local device. WhatAlbum
always reports it has no album information: MediaEntry (the player's now-playing model) has
no album field to report, an upstream data-model limitation rather than a missing lookup.
ShuffleOn and ShuffleOff are different: they act on the player (shuffle.set /
shuffle.unset), so they follow the same session gating as any other playback-affecting
command — only the local/"default" session may trigger them, unless the owning ovos-media
was configured with media.validate_source: false (see HiveMind: multi-session
gating). On a non-default session the intent handler itself
declines before emitting anything, and speaks a dialog saying the device cannot be controlled
from here, rather than reporting success for a shuffle change that never happened.
MPRIS Integration¶
ovos-media can register itself on D-Bus as an MPRIS player, so tools like playerctl and
desktop media widgets can control it, and can optionally reflect and take over other MPRIS
players already running on the machine. This only applies when running ovos-media, not the
default ovos-audio old-audioservice backend. The full setup, dbus verification steps, and
the Role A / Role B reflection-and-takeover behavior have moved to their own page:
ovos-media MPRIS Integration.
Configuration¶
{
"media": {
"enable_mpris": false, // expose OVOS playback on the MPRIS D-Bus interface
"mpris_poll_interval": 1, // seconds between MPRIS state polls (when enable_mpris)
"ignored_players": [], // MPRIS player names OVOS should not track/adopt
"dbus_type": "session", // "session" (per-user, default) or "system" D-Bus
// when true, the daemon only acts on the local ("default") session.
// set false for HiveMind/multi-session setups that drive playback remotely
"validate_source": true,
"preferred_audio_services": ["vlc", "mplayer", "cli"],
"preferred_video_services": ["vlc", "mplayer"],
"preferred_web_services": [],
// force playback through the audio players even for video/web media, e.g. on headless setups
// numeric PlaybackMode value from ovos_utils.ocp (30 = FORCE_AUDIO) or, since 2.0.0a1,
// the enum name as a string ("FORCE_AUDIO")
"playback_mode": 30,
"audio_players": {
"vlc": { "module": "ovos-media-audio-plugin-vlc", "aliases": ["VLC"], "active": true },
"cli": { "module": "ovos-media-audio-plugin-cli", "aliases": ["Command Line"], "active": true }
},
"video_players": {
"vlc": { "module": "ovos-media-video-plugin-vlc", "aliases": ["VLC"], "active": true }
},
"web_players": {
"mpv": { "module": "ovos-media-web-plugin-mpv", "aliases": ["mpv"], "active": true }
}
}
}
dbus_type picks which D-Bus the MPRIS integration registers and scans on: the per-user
session bus (default) or the system-wide system bus. playback_mode forces media that
would normally need a video or web player through the audio players instead (PlaybackMode
values such as FORCE_AUDIO) — useful on a headless/speaker-only device that has no screen to
show video on. web_players configures opm.media.web backends the same way audio_players
and video_players do, keyed by local name with a module entry-point name.
The
gui/browsermodule names shown in earlier drafts are not real backends. The bundled players arevlc,mplayer,cli,spotify,chromecast, and the legacyqt5GUI hand-off (see the backend table).
Installed backend plugins load automatically
audio_players/video_players/web_players are optional. An installed backend plugin
loads on its own. Use a {type}_players entry only to customize a plugin's name/aliases,
reorder it, or disable it ("active": false). Set "autoload_backends": false under
media to disable autoloading entirely. Remote{Audio,Video,Web}PlayerBackend subclasses
(a backend driving a remote target) never autoload; they always need an explicit
{type}_players entry, and configured entries always sort before autoloaded ones.
Each entry's key is a local name. module is the plugin's entry-point name
(e.g. ovos-media-audio-plugin-vlc), which can differ from its pip package name
(ovos-media-plugin-vlc). preferred_*_services are ordered fallback lists. The
audio list is also the generic fallback when a type-specific list is empty.
To hand playback to the legacy GUI player, add the qt5 entry points
(ovos-media-audio-plugin-qt5, …-video-plugin-qt5, …-web-plugin-qt5). That
backend is legacy and needs the deprecated ovos-shell.
Other media.* keys read by the player (all optional): autoplay (default
true, play the next queued track automatically) and merge_search (fold new
search results into the active playlist vs. replacing it). The old
force_audioservice key was removed in 2.0.0a1; use playback_mode instead.
First playback¶
ovos-media listens for ovos.common_play.play and expects a media dict plus an optional
playlist list:
{
"media": {
"uri": "https://example.com/song.mp3",
"title": "Some Jazz",
"media_type": 2,
"playback": 2
},
"playlist": [{"uri": "https://example.com/song.mp3", "title": "Some Jazz",
"media_type": 2, "playback": 2}]
}
media is required — handle_play_request logs a warning and ignores the message without it.
If playlist is omitted, ovos-media builds a single-track playlist containing only media.
Either way, playing then replaces the player's current playlist outright: an
ovos.common_play.play message always overwrites whatever was set by an earlier
ovos.common_play.playlist.set message, whether or not it includes a playlist key, and
next/previous navigation after a bare play (no playlist key) only ever has the one track to
work with. To play a track as part of a larger playlist, include the full track list under
playlist in the same ovos.common_play.play message.
Query-style bus messages follow a .response suffix convention: the reply to <msg_type> is
emitted as <msg_type>.response. For example, ovos.common_play.status is answered on
ovos.common_play.status.response, and ovos.common_play.track_info on
ovos.common_play.track_info.response.
Service-level bus messages¶
All service-level media control speaks ovos.common_play.* (2.0.0a1 dropped the per-player
ovos.audio.service.* / ovos.video.service.* bus surface). These bus messages
are handled inside the ovos-media process. The canonical reference for the full
ovos.common_play.* namespace is Bus Events Reference: OCP / media playback;
this section covers only the service-level subset with ovos-media-specific behavior notes.
Two are on the MediaService daemon itself, in _service_table()
(ovos_media/bus/api.py): ovos.common_play.ping, the one to use for a liveness probe, and
opm.audio.query (OPM plugin-discovery compatibility with the legacy PlaybackService
handler). ovos.common_play.home, .search.start and .search.end are pipeline-side
signals — the OCP pipeline plugin uses them to drive a GUI's own navigation/loading state.
ovos-media has no in-process GUI and nothing else that reacts to them, so neither the daemon
nor the player subscribes to any of the three; a bus message with no subscriber here is legal.
The rest are registered by OCPMediaPlayer:
| Bus message | Purpose |
|---|---|
ovos.common_play.seek |
Seek within the current track |
ovos.common_play.playlist.set / .queue / .clear |
Replace, append to, or empty the playlist |
ovos.common_play.shuffle.toggle / .set / .unset |
Toggle or explicitly set/unset shuffle mode |
ovos.common_play.repeat.toggle / .set / .unset |
Toggle or explicitly set/unset repeat mode |
ovos.common_play.duck / .unduck |
Duck/restore volume for a competing sound (e.g. TTS) |
ovos.common_play.cork / .uncork |
Pause/resume playback for a competing sound, without ducking |
ovos.common_play.like / .unlike |
Mark or unmark the current track as a liked song |
ovos.common_play.status |
Report full current player status |
ovos.common_play.disambiguation |
Report the candidate set from the last playback request, reply {"entries": [...]} |
ovos.common_play.likes |
Report the liked-songs store, reply {"entries": [...]} |
ovos.common_play.SEI.get |
Report the stream extractor identifiers ovos-media supports |
Ducking binds to ovos-audio's spec topics (ovos.audio.output.started / .ended trigger
duck/unduck since 1.0.0a1, which dropped the recognizer_loop:audio_output_* aliases), while
cork still listens on the legacy-style recognizer_loop:record_begin / record_end, since no
spec topic covers the mic-recording window. The full handler list, with each topic's gated value, is in _player_table()
(ovos_media/bus/api.py) — see HiveMind: multi-session gating
above.
ovos.common_play.playlist.set validates the payload before touching the playlist. A
non-list tracks value is ignored outright and the current playlist is kept. For a list
payload, each entry is checked on its own: invalid entries are skipped with a warning,
and the valid entries are applied.
Stop & Error Semantics¶
A few guarantees hold for OCPMediaPlayer regardless of which backend is active:
- Stop never advances the queue. Whether it comes from the bus API, a legacy stop topic, or
an MPRIS
Stopcommand — including while a bad-stream retry is still pending — an explicit stop always ends onPlayerState.STOPPEDwith the queue position unchanged. Advancing to the next track only ever happens through the normal end-of-track /nextpaths. - Unplayable tracks are skipped, not fatal. A track a backend cannot load emits
MediaState.INVALID_MEDIAand the player schedules a delayed retry that moves on to the next track in the queue, rather than failing the whole playback request outright. - A queue that is entirely broken stops instead of looping forever. If every track in the
current queue has already failed to load since the last successful one,
ovos-mediastops playback (PlayerState.STOPPED) instead of retrying indefinitely — this applies both toLoopState.REPEAT_TRACKon a single failed track and toLoopState.REPEATrestarting a wholly-failed queue. - End of queue emits
PlayerState.STOPPED. Reaching the end of the queue with repeat off updates the player state (and notifies the GUI/MPRIS/bus) rather than leaving it atPLAYINGwith nothing left to play. - Duplicate URIs in a playlist advance by position, not first match. A playlist like
[a, b, a]tracks the currently-playing entry by object identity, falling back to playlist position and then to URI lookup only if identity is unavailable — so playback moves through the queue in order instead of ping-ponging back to the first track sharing a URI.
ovos-media also speaks three failure dialogs, each guarded so it does not talk over itself:
no.playback.backendspeaks once per daemon lifetime, at the first play attempt made while zero audio, video, or web backends are loaded. An install with no backend plugin fails every play request the same way, so the daemon does not repeat the warning on later attempts.track.failedis rate-limited to once per queue, not once per skipped track, so a run of several broken tracks in a row does not talk over itself.queue.finishedspeaks only when the queue is genuinely exhausted: every track played through in order and none remain. It does not fire when autoplay is off mid-queue, and it does not fire when an external MPRIS player's track ends.
The track.failed guard resets on real evidence of playback starting, not on a track merely
loading, so a track that loads fine but fails to play does not reset the rate limit early. The
no.playback.backend guard never resets: it is a once-per-lifetime warning, not a per-queue one.
Shuffle mode honors the same failure bounds (0.4.13a1): a shuffled pick excludes tracks whose
uri already failed this queue, and when nothing playable remains — every track failed, or the
queue is empty and the current track failed — the player stops and speaks queue.finished,
the same ending as sequential playback. Repeat mode with at least one good track keeps
playing. A shut-down player also stops reacting to duck/unduck/cork on every spelling it
listens on (ovos.common_play.*, the ovos.audio.output.* duck triggers, and cork's
recognizer_loop:record_*), so a dead daemon no longer adjusts system volume.
MediaProvider plugins replace OCP skills¶
Media catalogs moved out of skills and into plugins. The MediaProvider plugin type
(opm.media.provider / PluginTypes.MEDIA_PROVIDER) is what the OCP pipeline
loads in-process and calls search() on. See that page for the loading mechanism, the
media_providers config key, and how results merge with the legacy bus @ocp_search path.
The corresponding OCP skills for the eleven media sources below are archived. Install the MediaProvider plugin instead:
| MediaProvider plugin | Replaces (archived) |
|---|---|
ovos-media-provider-bandcamp |
ovos-skill-bandcamp |
ovos-media-provider-pyradios |
ovos-skill-pyradios |
ovos-media-provider-somafm |
ovos-skill-somafm |
ovos-media-provider-soundcloud |
ovos-skill-soundcloud |
ovos-media-provider-tunein |
ovos-skill-tunein |
ovos-media-provider-youtube |
ovos-skill-youtube |
ovos-media-provider-youtube-music |
ovos-skill-youtube-music |
ovos-media-provider-mass |
ovos-skill-music-assistant (playback via the companion ovos-media-plugin-mass backend) |
ovos-media-provider-news |
ovos-skill-news |
ovos-media-provider-spotify |
ovos-skill-spotify (playback via the companion ovos-media-plugin-spotify backend) |
ovos-media-provider-local |
Local file playback; uses tinytag (MIT) rather than mutagen (GPL) for metadata |
An OCP skill that is not in this table is unaffected. MediaProvider plugins only replace the eleven catalogs above. Skill-based media search remains a supported way to provide media for anything else.
Legacy Compatibility & Known Coupling Issues¶
ovos-media's pipeline plugin falls back to the classic mycroft.audio.service.* bus messages
when ovos-media is not running, and bridges old pre-OCP CommonPlaySkill skills via play:query
/ play:start. It also carries some architectural coupling from that history, such as
OCPVoiceSkill registering itself as a skill and how the Music Assistant backend's
next()/previous() delegate to MA's own queue API. The full detail has moved to its own page:
ovos-media Legacy Compatibility.
HiveMind: multi-session gating¶
On a server that fans playback out to several HiveMind satellites, not
every bus handler should act on behalf of every session. ovos_media/bus/api.py's single
registration table marks each playback-executing topic gated, and applies is_default_session()
(ovos_media/utils.py) before dispatching it. Every ovos.common_play.* topic that changes
player state is gated to the local ("default") session this way — the full playback-control set
(play, pause, play_pause, resume, stop, next, previous, seek,
set_track_position), the whole playlist.* group (set, clear, queue), the hardware-bound
duck/unduck/cork/uncork (which react to ovos.audio.output.started/.ended and
recognizer_loop:record_begin/_end), the whole shuffle.* and repeat.* groups,
like/unlike, and ovos.utterance.handled. Only the read-only/status topics
(get_track_length, get_track_position, track_info, list_backends, status,
media.state, playback_time, SEI.get) and a few others stay ungated — check
_player_table() in ovos_media/bus/api.py for the authoritative, current list rather than
treating this paragraph as exhaustive. Set media.validate_source: false on an instance that
must act on non-default/remote HiveMind sessions — see
Updating: deployers.
The media.validate_source config flag (see Configuration above) controls how
strict this gating is:
true(default): the daemon only acts on messages carrying the local "default" session. Correct for a single-device install whereovos-mediaand the microphone are the same box.false: needed on a server fronting multiple HiveMind satellites, so a satellite's own session can drive playback remotely instead of every command being silently scoped to "default".
Get this wrong on a server/satellite topology and playback commands from a satellite either get
ignored (validate_source: true on a multi-satellite server) or, worse, target the wrong
device's player state.
Features¶
Liked Songs¶
Like a currently playing song through the GUI, or over the bus with ovos.common_play.like.
The voice route is disabled today: like_song.intent and play_favorites.intent are
commented out of the OCP pipeline's intent list, so "I like that song" and "play my favorite
songs" never match. Use the GUI or the bus messages until they are re-enabled.
A like request with nothing playing and no explicit uri is refused, with spoken
feedback, instead of silently storing a junk entry with no way to remove it later.
Skills Browse / Featured Media¶
Some skills provide @ocp_featured_media(). These are accessible from the OCP skills menu in the GUI.
File Browser Integration¶
Selected files and folders will be played in OCP. Folders are treated as playlists.
Read next: Screens on OVOS Today · Concepts Overview Related: Audio Service · ovos-media OCP Pipeline Plugin · OCP Pipeline · Media Playback Plugins · OCP Extractors · ovos-media MPRIS Integration · ovos-media Legacy Compatibility