Extensions let you add new visual experiences and interactions to Sono. They run in a sandboxed Lua environment with access to APIs that expose player state, real-time audio data, the local library, persistent storage, and a 2D drawing canvas.
An extension is a .sopk file; a ZIP archive containing at minimum:
manifest.json: describes the extension and declares its requirements- An entry Lua file (default:
main.lua)
{
"id": "com.example.my_extension",
"name": "My Extension",
"version": "1.0.0",
"sono_sdk": ">=1.0.0",
"author": "Your Name",
"description": "Short description of what this does",
"entry": "main.lua",
"type": "tool",
"permissions": ["player.read", "ui.screen"],
"hooks": ["onTrackChanged"],
"ui_mode": "canvas"
}| Field | Required | Description |
|---|---|---|
id |
yes | Unique reverse-domain identifier, e.g. com.example.ext |
name |
yes | Display name shown in the UI |
version |
yes | SemVer string, e.g. 1.0.0 |
sono_sdk |
yes | Version constraint for the Sono SDK, e.g. >=1.0.0 |
author |
yes | Author name |
description |
yes | One-line description |
entry |
yes | Path to the Lua entry point, relative to the archive root |
type |
yes | One of: feature lyrics tracker widget tool |
permissions |
no | Array of permission strings the extension needs |
hooks |
no | Array of lifecycle hook names to subscribe to |
ui_mode |
no | canvas for custom drawing, rfw for Remote Flutter Widgets |
ui_file |
no | Path to the RFW definition file (rfw mode only) |
ui_overlay |
no | true to render the canvas as a full-screen overlay |
Extensions must declare every API they intend to use. APIs for undeclared permissions are not registered and will cause a nil error if called.
| Permission | APIs unlocked |
|---|---|
player.read |
sono.player.* (read-only) |
player.control |
sono.player.play(), pause(), seek(), next(), previous(), setSpeed() |
audio.fft |
sono.audio.getSpectrum() |
library.read |
sono.library.* |
storage |
sono.storage.* |
ui.screen |
sono.canvas.* and sono.ui.* |
All functions are optional. The runtime only calls them if they exist.
| Function | When called |
|---|---|
sono_init() |
Once after the extension activates |
sono_onDraw(w, h) |
Every frame (~60 fps) when the canvas UI is visible |
sono_onTrackChanged(track) |
When the current track changes |
sono_onPlaybackStateChanged(playing) |
When playback starts or pauses |
sono_dispose() |
When the extension is deactivated or uninstalled |
Hooks must also be listed in the manifest's hooks array to receive calls for onTrackChanged and onPlaybackStateChanged.
Requires player.read.
| Function | Returns | Description |
|---|---|---|
sono.player.getCurrentTrack() |
track or nil |
The currently loaded track |
sono.player.isPlaying() |
bool |
Whether playback is active |
sono.player.getPosition() |
number |
Current position in milliseconds |
sono.player.getDuration() |
number |
Track duration in milliseconds |
Track object fields: id, title, artist, album, durationMs, path
Requires player.control (in addition to player.read).
| Function | Description |
|---|---|
sono.player.play() |
Start playback |
sono.player.pause() |
Pause playback |
sono.player.playPause() |
Toggle play/pause |
sono.player.seek(ms) |
Seek to a position in milliseconds |
sono.player.next() |
Skip to next track |
sono.player.previous() |
Skip to previous track |
sono.player.setSpeed(speed) |
Set playback speed (1.0 = normal) |
Requires audio.fft. Android only; returns an empty table on other platforms.
| Function | Returns | Description |
|---|---|---|
sono.audio.getSpectrum() |
table |
1-indexed table of FFT magnitudes (0..1), ~64 bins |
The spectrum is updated at ~30 fps. Index 1 is the lowest frequency; higher indices correspond to higher frequencies. Safe to call every draw frame.
Requires library.read.
| Function | Returns | Description |
|---|---|---|
sono.library.getAllSongs() |
table |
Array of all song objects in the library |
sono.library.getAlbums() |
table |
Array of album objects |
sono.library.getFavoriteSongIds() |
table |
Array of favourite song IDs |
Song fields: id, title, artist, album, durationMs, path
Album fields: id, album, artist, numOfSongs
Requires storage. Per-extension key-value store, persisted to disk between sessions.
| Function | Description |
|---|---|
sono.storage.set(key, value) |
Store a value (string, number, or boolean) |
sono.storage.get(key) |
Read a stored value (nil if absent) |
sono.storage.delete(key) |
Remove a single key |
sono.storage.clear() |
Remove all stored data for this extension |
Requires ui.screen and ui_mode: "canvas". Only valid inside sono_onDraw(w, h).
All color arguments are RGBA integers in the range 0–255. Coordinates are in logical pixels with (0, 0) at the top-left.
| Function | Description |
|---|---|
sono.canvas.clear(r, g, b, a) |
Fill the entire canvas with a color |
sono.canvas.drawRect(x, y, w, h, r, g, b, a) |
Draw a filled rectangle |
sono.canvas.drawCircle(cx, cy, radius, r, g, b, a) |
Draw a filled circle |
sono.canvas.drawLine(x1, y1, x2, y2, r, g, b, a, strokeWidth) |
Draw a line |
sono.canvas.drawText(text, x, y, r, g, b, a, fontSize) |
Draw text |
The w and h parameters passed to sono_onDraw give the canvas dimensions for the current frame.
Requires ui.screen and ui_mode: "rfw". Used to push data from Lua into a Remote Flutter Widget tree.
| Function | Description |
|---|---|
sono.ui.setData(key, value) |
Push a value into the Flutter widget tree |
sono.ui.getData(key) |
Read a value previously set |
manifest.json
{
"id": "com.example.pulse",
"name": "Pulse",
"version": "1.0.0",
"sono_sdk": ">=1.0.0",
"author": "You",
"description": "A circle that reacts to the bass",
"entry": "main.lua",
"type": "tool",
"permissions": ["player.read", "audio.fft", "ui.screen"],
"hooks": ["onPlaybackStateChanged"],
"ui_mode": "canvas"
}main.lua
local radius = 40
local playing = false
function sono_init()
playing = sono.player.isPlaying()
end
function sono_onPlaybackStateChanged(p)
playing = p
end
function sono_onDraw(w, h)
sono.canvas.clear(10, 10, 10, 255)
if playing then
local spec = sono.audio.getSpectrum()
local bass = (spec[1] or 0) + (spec[2] or 0) + (spec[3] or 0)
radius = radius * 0.8 + (40 + bass * 80) * 0.2
end
sono.canvas.drawCircle(w * 0.5, h * 0.5, radius, 255, 72, 147, 200)
endPut your manifest.json and Lua files into a ZIP archive and rename it to .sopk:
zip -j my_extension.sopk manifest.json main.luaInstall it via Settings > Extensions > Install .sopk.
REMEMBER: This is an early version of Sono Extensions. Things will probably change.