Plugin distribution
How Vivi downloads, verifies, updates and removes plugins, and why it uses mpv script packages today and WASM later.
What stays built in
A small core ships inside the binary: Music Visualizer, Skip Intro & Credits, Lyrics, Subtitle Search, Auto-tagger, Free Music and Network Streams. They draw Slint UI (visualizer canvas, Skip button, karaoke overlay, search and preview dialogs) that a script cannot render; the two music plugins also write tags with lofty. The ListenBrainz Scrobbler needs no UI, so it is a downloadable package. Discord Rich Presence and Remote Control are packages too: a Lua script plus a small native helper per platform that talks to mpv over its JSON IPC.
Keys: the Auto-tagger works without keys (MusicBrainz search by tags or file name, Cover Art Archive); an optional free AcoustID application key adds audio-fingerprint matching. Free Music searches the Internet Archive without a key; Jamendo needs a free client ID. The scrobbler needs your ListenBrainz user token. MusicBrainz is queried at most once per second with a descriptive User-Agent.
Everything else is a downloadable package, so the app stays small and plugins update on their own schedule.
Options considered
mpv Lua/JS packages — now
mpv already embeds Lua and JavaScript, so packages add no binary size. Full player API, key bindings,
OSD overlays, audio/video filters, mp.options settings. No sandbox and mpv cannot unload a
script, so disable/update take effect after a restart.
WASM (Extism/wasmtime) — later
Real sandbox with enforced permissions and a host API (player control, allow-listed HTTP, settings,
declarative UI messages drawn by Slint). Costs ~10–20 MB and a stable API to maintain. The
kind field reserves wasm, so catalog and installer will not change.
Native libraries — rejected
Rust has no stable ABI (a rebuild per Vivi version, compiler and platform) and native code can neither be sandboxed nor kept from crashing the player.
Package format
A zip named <id>-<version>.zip with manifest.toml at its root and the entry
script <id>.lua:
id = "night-mode" # lowercase-with-dashes
name = "Night Mode"
version = "1.0.0" # semver
description = "..."
author = "Vivi"
kind = "mpv-lua" # mpv-lua | mpv-js (wasm reserved)
entry = "night-mode.lua"
min_vivi = "0.8.0"
icon = "night"
lucide = "moon"
permissions = [] # network | filesystem | subprocess
[[settings]]
key = "strength"
title = "Strength"
type = "choice" # toggle | choice | text | secret | action
options = ["Medium", "Light", "Strong"]
Settings appear in Settings → Extensions and reach the script as script-opts
(<id>-<key>), updated live; action settings arrive as script messages. Permissions are
shown before install; with mpv scripts they are reviewed declarations, not a sandbox.
Catalog
index.json (from plugins/index.json) lists built-in plugins and
packages. A package entry carries version, min_vivi, permissions,
size, the url of a GitHub release asset (tag plugin-<id>-v<version>)
and its sha256. Zips are deterministic, so the committed sha256 equals what CI uploads.
A minisign signature field is reserved.
Publishing
The Plugins workflow checks Lua syntax, verifies index.json is current, creates a
release for every new plugin-<id>-v<version> (never marked “latest”) and re-downloads
each asset to verify its sha256. The docs workflow publishes the catalog. See
Package & publish.
Install, update, rollback
- Where:
~/.local/share/vivi/plugins/<id>/<version>/;<id>/currentnames the active version. - Update check: at startup at most once a day (can be turned off) and with Check now. A toast announces updates.
- Install on a background thread: download → sha256 must match → unpack to a staging folder (no
.., absolute paths or symlinks; size caps) → manifest must match the catalog andmin_vivi→ rename into place → atomically switchcurrent. Any failure leaves the old version. - Permissions prompt before installing a package that asks for any (or an update adding some).
- Rollback to the previous version, which is kept; older ones are pruned.
- Uninstall removes the folder; settings stay in
config.toml. - A new install loads immediately; disable, update, rollback and uninstall apply after a restart because mpv cannot unload scripts.
Full design notes: docs/plugins-architecture.md.