Music providers
A music provider fills the library, like the built-in Local files and Navidrome. Users pick it in Settings → Library → Music provider.
local provider = { id = "my_server", name = "My server", description = "Songs from my server", icon = "icon.svg", streams = true, -- songs play from URLs, and can be downloaded for offline use}
function provider.get_tracks(self, done) nari.http({ url = "https://example.com/tracks" }, function(response) done(response and nari.json_decode(response.body) or {}) end)end
function provider.playable(self, tracks) local list = {} for _, track in ipairs(tracks) do table.insert(list, { id = track.id, title = track.title, artist = track.artist, album = track.album, duration = track.duration, uri = "https://example.com/stream/" .. track.id, }) end return listend
nari.music_provider(provider)Every function gets the provider as its first argument. nari.music_provider returns the same table: emit its
library_changed, library_ready(success, error), library_progress and settings_changed signals when the library
or the provider’s settings change.
The last argument of async functions is done: return the result right away, or call done(result) later.
Functions
Section titled “Functions”Every function is optional; missing ones return empty values.
| Function | Kind | Returns |
|---|---|---|
is_configured() |
sync | true when the provider can load, like when it has a server address. |
is_loading(), is_ready() |
sync | The library’s loading state. |
get_status() |
sync | A dictionary shown while loading. |
setup_entries(), settings_entries() |
sync | Settings entries for the welcome screen and Settings → Library. |
get_tracks(), get_albums(), get_artists() |
async | Lists of songs, albums and artists. |
get_album(id), get_artist(id) |
async | One album (with tracks) or artist (with albums and tracks, plus the keys in Artist screens). |
get_song(id) |
async | One song. |
get_raw_lyrics(id) |
async | Embedded lyrics, {} when there are none. |
playable(tracks) |
async | What the player needs to play tracks. See Playable songs. |
media_uri(track, quality) |
sync | The uri to download track with, for the Download quality setting (original, high, normal or low). Giving it also shows Stream quality: read SettingsManager.playback.stream_quality in playable. |
get_cover_path(kind, id, thumb) |
async | A local image path for an "album", "artist", "song" or "playlist". |
search(query, filters) |
async | { tracks, albums, artists } for the Search screen. filters has the tags the user added: title, artist, album, playlist, date (a year or a range like 2010-2015) and genre. Leave it out to let Nari search your lists. |
get_genres() |
async | Genre names for the Search screen, most relevant first. |
get_playlists(), get_playlist(id), create_playlist(name) |
async | Playlists (id, name, song_count, duration, modified, cover_album_ids, image; get_playlist adds tracks). create_playlist returns the new id. |
delete_playlist(id), rename_playlist(id, name), add_to_playlist(id, song_ids), remove_from_playlist(id, song_id), set_playlist_image(id, path) |
async | Call done(true) when the change is saved. Nari reloads the playlist then. |
get_discover() |
async | Sections for Discover, like nari.discover_section specs with items as a list. Set auto_discover = false on the provider to show only these. |
get_radio(kind, id) |
async | Songs for a radio started from a "track" or an "artist". Leave it out and Nari builds one from the library. |
get_page(kind, context_id, offset, limit) |
async | For big libraries. |
get_liked(kind), set_liked(kind, id, liked) |
async | Likes kept by the service (track, album or artist). Without them, Nari keeps likes on the device. |
Songs use the same keys as the song fields. Albums have id, title, artist,
artist_id, year, song_count and duration; artists have id, name, album_count and song_count.
Playable songs
Section titled “Playable songs”playable entries have id, title, artist, artist_id, album, album_id, duration (ms) and:
| Key | |
|---|---|
uri |
A URL, a local path, or scheme:id for a resolver or byte source. |
file_name |
Its extension helps pick the decoder. |
live |
true for radio streams. |
cover, cover_url |
A local image path, or a link. |
replay_gain |
In dB, or nil. |
public_cover_url, share_url |
Optional https:// image and page anyone can open, shown in Discord Rich Presence. Leave them out when they need sign in. |
HTTP streams are downloaded one mebibyte at a time when the server supports it, so hosts that throttle long downloads still play smoothly, and seeking ahead jumps the download there.
Artist screens
Section titled “Artist screens”The artist screen shows a header (photo, name, listeners) and sections: Popular, Albums, Singles & EPs,
Compilations, Appears on, Songs, Fans also like and About. get_artist can fill them with these optional keys:
| Key | |
|---|---|
top_tracks |
Songs for Popular, most played first. |
listeners |
A number shown as “6.4M listeners”. |
bio |
Plain text for About. |
related |
Artists for Fans also like. Their id opens their screen through get_artist. |
similar |
Artist names instead of related; Nari shows the ones in the library. |
sections |
More sections, like Discover sections with items as a list. order places them among the built-in ones (Popular 0, Albums 10, Singles 20, Compilations 30, Appears on 35, Songs 40, Fans also like 50, About 90), 45 by default. |
Albums are split by their release_type: single and ep go to Singles & EPs, compilation to Compilations,
appears_on to Appears on, everything else to Albums. With Artist info from the internet on, Nari fills what’s
missing from Deezer and Last.fm.
function provider.get_artist(self, id, done) fetch_artist(id, function(artist) artist.top_tracks = artist.popular artist.listeners = artist.monthly_listeners artist.sections = { { title = "This Is " .. artist.name, kind = "playlists", items = { artist.this_is } }, } done(artist) end)endBig libraries
Section titled “Big libraries”Services with huge libraries shouldn’t return every song from get_tracks. Set paged = true and give get_page
instead: lists then load as they’re scrolled.
provider.paged = true
function provider.get_page(self, kind, context_id, offset, limit, done) -- kind: "tracks", "albums", "artists", or "playlist" (then context_id is the playlist) nari.http({ url = "https://example.com/" .. kind .. "?offset=" .. offset .. "&limit=" .. limit }, function(response) local page = nari.json_decode(response.body) done({ items = page.items, more = page.next ~= nil }) end)endmore says whether there’s another page; when left out, a full page means there’s more. With a paged provider:
- give
search, since Nari can’t search a library it doesn’t have get_album,get_artistandget_songstill return whole items- playing or shuffling a whole list loads up to 1000 songs of it