Getting started

September 9, 2026 · View on GitHub

ovos-media is the OCP-native media daemon for OpenVoiceOS. It plays the audio/video/web half of the media stack while ovos-audio continues to handle TTS, the two run side by side. Apache-2.0, Python 3.10+.


Installation

From PyPI

pip install ovos-media

Using uv:

uv pip install ovos-media

Install at least one playback backend

ovos-media does not play media itself, it delegates to a backend plugin. Install at least an audio backend so something can be heard:

pip install ovos-media-plugin-vlc        # or -mplayer / -simple / -spotify / -chromecast

ovos-media-plugin-vlc wraps python-vlc, which needs the native libvlc system library. pip install alone will not error if libvlc is missing — the plugin just silently fails to load, and no audio backend is available. Install the system package too, e.g. apt install vlc (Debian/Ubuntu) or libvlc-dev/libvlc5 on distros that split the runtime from the dev headers.

For a headless box, ovos-media-plugin-ffplay is a good default: it only needs ffmpeg/ffplay on the PATH, which most systems already have or can install with a single package (apt install ffmpeg), with no separate native binding to install.

See Backends for the full list of audio/video/web backends.

Development / editable install

git clone https://github.com/OpenVoiceOS/ovos-media
uv pip install -e ovos-media

Enable it

ovos-media takes over media playback; ovos-audio keeps handling TTS. Turn off the legacy audio service so the two don't both try to play media:

{
  "enable_old_audioservice": false
}

Place this in ~/.config/mycroft/mycroft.conf (or ~/.config/ovos/ovos.conf). Keep ovos-audio running for TTS, and start ovos-media as its own process.

A complete migration walkthrough, including configuration mapping from the old audio service, is in the migration guide.

Note the naming split when you list backends in config (see Backends): the pip package for OCP audio/video plugins uses -plugin- (e.g. ovos-media-plugin-vlc), but the module name you write in config uses -audio-plugin- / -video-plugin- (e.g. ovos-media-audio-plugin-vlc for audio, ovos-media-video-plugin-vlc for video). Installing the pip package is not enough on its own — the module key in media.audio_players / media.video_players must match the entry-point name, not the pip package name.

Testing in isolation

ovos-config resolves mycroft.conf/ovos.conf through the standard XDG base directories (ovos_config/locations.py, via ovos_utils.xdg_utils.xdg_config_home), so setting XDG_CONFIG_HOME to a throwaway directory before starting ovos-media runs it against an isolated config, without touching your real user config:

export XDG_CONFIG_HOME=/tmp/ovos-media-test/config
mkdir -p "$XDG_CONFIG_HOME/mycroft"
echo '{"enable_old_audioservice": false}' > "$XDG_CONFIG_HOME/mycroft/mycroft.conf"
ovos-media

Model and plugin caches follow XDG_CACHE_HOME separately (same ovos_utils.xdg_utils module, xdg_cache_home). Overriding only XDG_CONFIG_HOME isolates configuration but not caches — set both env vars if you need a fully isolated test environment.


Run the daemon

The package installs an ovos-media console entry point:

ovos-media

The process connects to the MessageBus, initialises OCPMediaPlayer, and signals readiness via the ProcessStatus machinery (ovos_media/__main__.py).

Running it embedded

from ovos_utils import wait_for_exit_signal
from ovos_media.service import MediaService

svc = MediaService()   # connects to the bus, builds OCPMediaPlayer
svc.daemon = True
svc.start()            # starts the daemon thread; run() marks ProcessStatus READY
wait_for_exit_signal()
svc.shutdown()

MediaService.__init__ reads the media section of mycroft.conf / ovos.conf, opens (or reuses) a MessageBusClient, creates an OCPMediaPlayer, and wires up the bus handlers. The thread's run() emits the READY status signal. This is exactly what the ovos-media console entry point (ovos_media/__main__.py) does.


First playback

With a full OVOS install, ask OVOS to play something and the OCP pipeline (in ovos-core) classifies the request, queries the installed media providers, and routes the winning result to ovos-media:

"play some jazz"

from ovos_bus_client import MessageBusClient, Message

bus = MessageBusClient()
bus.run_in_thread()

bus.emit(Message("recognizer_loop:utterance",
                 {"utterances": ["play some jazz"], "lang": "en-us"}))

This needs ovos-core (or another OCP pipeline) running to turn the utterance into a play request. With only ovos-media + ovos-messagebus + a backend (no ovos-core, no skills), there is no pipeline to classify "play some jazz", so send the play request directly instead. ovos-media listens for ovos.common_play.play on the bus and expects a media dict plus an optional playlist list:

from ovos_bus_client import MessageBusClient, Message

bus = MessageBusClient()
bus.run_in_thread()

track = {
    "uri": "https://example.com/song.mp3",
    "title": "Some Jazz",
    "media_type": 2,     # MediaType.MUSIC
    "playback": 2        # PlaybackType.AUDIO
}

bus.emit(Message("ovos.common_play.play", {
    "media": track,
    "playlist": [track]
}))

media is required (handle_play_request in ovos_media/player/ logs a warning and ignores the message if it is missing). media_type and playback are the integer values of the ovos_utils.ocp.MediaType and ovos_utils.ocp.PlaybackType enums; common ones are MediaType.AUDIO (1), MediaType.MUSIC (2), MediaType.VIDEO (3), MediaType.PODCAST (6), and PlaybackType.AUDIO (2), PlaybackType.VIDEO (1), PlaybackType.AUDIO_SERVICE (3). See ovos_utils/ocp.py for the full list. Values can also be sent as the enum's string name in most OCP tooling, but the handler here reads them straight off message.data, so plain integers (or the raw enum) are the safest choice.

If playlist is omitted, ovos-media builds a single-track playlist containing only media (playlist = message.data.get("playlist") or [media] in handle_play_request). Either way, play_media then calls self.playlist.replace(playlist), which replaces the player's current playlist outright. This means an ovos.common_play.play message always overwrites whatever was set by a previous ovos.common_play.playlist.set message, whether or not you include a playlist key — next/previous track navigation after a bare play (no playlist key) will only ever have 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.

To confirm the daemon is live, ping it:

bus.emit(Message("ovos.common_play.ping"))
# expect an "ovos.common_play.pong" reply

(handled by the ping handler in OCPBusApi).

Querying status

Every reply on the bus uses the .response suffix convention: the reply to <msg_type> is emitted as <msg_type>.response (see Message.response in ovos-bus-client). To query full player status, send ovos.common_play.status and wait for ovos.common_play.status.response (handled by OCPMediaPlayer.handle_status in ovos_media/player/):

from ovos_bus_client import MessageBusClient, Message

bus = MessageBusClient()
bus.run_in_thread()

reply = bus.wait_for_response(Message("ovos.common_play.status"),
                               reply_type="ovos.common_play.status.response",
                               timeout=5)
print(reply.data)
# {'playback_type': ..., 'media_type': ..., 'player_state': ...,
#  'loop_state': ..., 'media_state': ..., 'shuffle': ...,
#  'playlist_position': ..., 'playlist_size': ..., 'title': ...,
#  'artist': ..., 'image': ...}

Other single-value queries follow the same pattern, for example ovos.common_play.track_info (replies on ovos.common_play.track_info.response with the current track's metadata), ovos.common_play.get_track_length / ovos.common_play.get_track_position, and ovos.common_play.list_backends.


Where things live next

  • Find media, install media providers (opm.media.provider) to give OVOS catalogs to search.
  • Play media, install backends (opm.media.audio / .video / .web) and pick preferences in configuration.
  • Control from the desktop, enable MPRIS to drive playback from playerctl, KDE Connect, or the GNOME media widget.

Runtime dependencies

PackagePurpose
ovos-utilsOCP enumerations, MessageBus helpers, process utilities
ovos-bus-clientWebSocket MessageBus client
ovos-configConfiguration loader (mycroft.conf / ovos.conf)
ovos-plugin-managerBackend and media-provider plugin discovery via entry points
ovos-workshopOVOSCommonPlaybackSkill base used by the built-in liked-songs catalog, plus OCP decorators
json-databasePersistent liked-songs and play-history storage (JsonStorageXDG)
dbus-nextAsync D-Bus implementation used by the MPRIS exporter

The extras install (pip install ovos-media[extras]) adds stream-extractor plugins for YouTube, M3U, RSS, local files, and news feeds.


Supported Python versions

Python 3.10 and above, enforced by requires-python = ">=3.10" in pyproject.toml.


← Glossary · Home · Architecture →