Now Playing Logger Plugin (Python)
A Python example plugin that demonstrates the Scheduler and SubsonicAPI host services by periodically logging what is currently playing in Navidrome.
Features
- Uses
scheduler_schedulerecurringhost function to set up a recurring task - Uses
subsonicapi_callhost function to query thegetNowPlayingAPI - Configurable cron expression and user via plugin config
- Demonstrates Python host function imports using
@extism.import_fn
Prerequisites
- extism-py - Python PDK compiler
curl -Ls https://raw.githubusercontent.com/extism/python-pdk/main/install.sh | bash
Note:
extism-pyrequires Binaryen (wasm-merge,wasm-opt) to be installed.
Building
From the plugins/examples directory:
make nowplaying-py.ndp
Or directly:
extism-py plugin/__init__.py -o plugin.wasm
zip -j nowplaying-py.ndp manifest.json plugin.wasm
Installation
-
Copy
nowplaying-py.ndpto your Navidrome plugins folder -
Enable plugins in
navidrome.toml:[Plugins] Enabled = true Folder = "/path/to/plugins" -
Configure the plugin in the UI (Settings → Plugins → nowplaying-py)
Configuration
| Key | Description | Default |
|---|---|---|
cron |
Cron expression for check frequency | */1 * * * * |
user |
Navidrome user for SubsonicAPI | admin |
Testing
Test the manifest:
extism call nowplaying-py.wasm nd_manifest --wasi
Output
When running, the plugin logs messages like:
🎵 john is playing: Pink Floyd - Comfortably Numb (The Wall)
🎵 jane is playing: Radiohead - Paranoid Android (OK Computer)
Or when no one is playing:
🎵 No users currently playing music
How It Works
-
Initialization (
nd_on_init): Reads the cron expression from config and schedules a recurring task using the Scheduler host service. -
Callback (
nd_scheduler_callback): When the scheduled task fires, calls the SubsonicAPIgetNowPlayingendpoint and logs the results.
Host Function Usage (Python)
This plugin demonstrates how to call Navidrome host functions from Python:
import extism
import json
# Import the host function
@extism.import_fn("extism:host/user", "subsonicapi_call")
def _subsonicapi_call(offset: int) -> int:
"""Raw host function - returns memory offset."""
...
# Wrapper for JSON marshalling
def subsonicapi_call(uri: str) -> dict:
request = {"uri": uri}
request_bytes = json.dumps(request).encode('utf-8')
request_mem = extism.memory.alloc(request_bytes)
response_offset = _subsonicapi_call(request_mem.offset)
response_mem = extism.memory.find(response_offset)
response = json.loads(extism.memory.string(response_mem))
if response.get("error"):
raise Exception(response["error"])
return json.loads(response.get("responseJSON", "{}"))