Radio Browser 1.0.3 — three fixes I made locally (with AI assistance)

-–

Radio Browser 1.0.3 — three fixes I made locally (with AI assistance)

Hi all,

I’m running Volumio 4.119 on a Raspberry Pi 4 (armhf, Node 20.5.1) with the
Radio Browser plugin 1.0.3, and I ran into three problems that all turned out to be
small mismatches with Volumio’s own conventions rather than anything wrong with the
RadioBrowser API.

I fixed them on my own box and everything works here now. Full disclosure: I worked these
out with the help of an AI assistant
— it read Volumio’s core files with me (playlistManager.js,
statemachine.js, the mpd and webradio plugins) and wrote the patches, I tested and verified
them on the running system. I’m not a Node developer, so please treat the code as a
working starting point rather than a finished pull request.

I could not work out who currently maintains this plugin. package.json names
Markus F.J. Busche as author and points at github.com/elpatron68/volumio-plugins-sources,
but I found no clear maintainer contact or recent activity to file this against, and nothing
about these issues on the forum. So I’m posting it here in the hope it reaches the right
person — happy to open an issue or PR instead if someone tells me where.

All three patches are in /data/plugins/music\_service/radio\_browser/index.js.

-–

1. “Add to favourites” and “Add to playlist” silently do nothing

Symptom. Searching and playing works fine. The heart icon and the playlist dialog appear,
you click them, no error is shown anywhere — and nothing is added. The heart stays dull.

Cause — two things at once.

(a) Volumio expects explodeUri to resolve an array of tracks. PlaylistManager.commonAddToPlaylist
reads info\[0]. The plugin resolves a bare object, so info\[0] is undefined and the add
fails without ever throwing something visible.

(b) PlaylistManager.addToFavourites only routes an item into the radio-favourites list when
service === 'webradio'. Our items carry service: 'radio\_browser', so even after (a) is fixed
they would land in the generic favourites list instead of the radio favourites.

Patch (a) — in explodeUri, wrap the resolved object in an array:

// Volumio expects explodeUri to resolve an ARRAY of tracks.
// playlistManager.commonAddToPlaylist reads info\[0]; a bare object
// makes that undefined and silently breaks favourites/playlists.
defer.resolve(\[{
    uri: station.url\_resolved || station.url,
    service: 'webradio',
    name: station.name,
    title: station.name,
    type: 'webradio',
    albumart: station.favicon || '',
    trackType: station.codec || 'webradio',
    channels: 2,
    duration: 0
}]);

Patch (b) — add an addToFavourites method to the controller (the plugin has none today):

// Volumio's PlaylistManager.addToFavourites only writes to 'radio-favourites' when
// service === 'webradio'. Our items carry service 'radio\_browser', so without this
// method the station would land in the generic 'favourites' list. Resolve the station
// here and store it as a plain web radio, which is what it is.
ControllerRadioBrowser.prototype.addToFavourites = function (data) {
    var self = this;
    var defer = libQ.defer();

    self.explodeUri(data.uri)
        .then(function (exploded) {
            var track = Array.isArray(exploded) ? exploded\[0] : exploded;
            if (!track || !track.uri) {
                defer.reject(new Error('Could not resolve station ' + data.uri));
                return;
            }

            var plm = self.commandRouter.playListManager;
            plm.commonAddToPlaylist(
                plm.favouritesPlaylistFolder,
                'radio-favourites',
                'webradio',
                track.uri,
                track.name || track.title,
                track.albumart || ''
            )
                .then(function (result) { defer.resolve(result); })
                .fail(function (err) { defer.reject(err); });
        })
        .fail(function (err) {
            self.logger.error('RadioBrowser addToFavourites error: ' + err);
            defer.reject(err);
        });

    return defer.promise;
};

Patch (c) — do not skip this one. Because explodeUri now returns an array, clearAddPlayTracks
must be adjusted too, or playback breaks:

return self.explodeUri(track.uri)
    .then(function (exploded) {
        // explodeUri resolves an array; keep tolerating a bare object
        var resolved = Array.isArray(exploded) ? exploded\[0] : exploded;
        if (!resolved || !resolved.uri) return libQ.resolve();
        var streamUrl = resolved.uri;
        // ... unchanged from here

-–

2. The bitrate shown while playing is a database label, not a measurement

Symptom. Whatever station I played, the player kept showing the same numbers — in my case
“192 kbps / 16 bit”. It looked like a measurement, and it was wrong.

Cause. The plugin writes RadioBrowser’s community-maintained station.bitrate field into
Volumio’s samplerate field, and hardcodes bitdepth: '16 bit':

samplerate: (station.bitrate ? station.bitrate + ' kbps' : ''),
bitdepth: '16 bit',

That value is a label from the database, often stale or 0, and it has nothing to do with what
is actually coming down the wire. The reason it survives is the second half: statemachine.js
only clears the meaningless samplerate/bitdepth fields when the queue item says
service: 'webradio'. The plugin said service: 'mpd', so the static label was never cleared
and MPD’s real, measured bitrate never reached the UI.

Patch. Already shown in patch (a) above — the two relevant lines are:

    // 'webradio', not 'mpd': statemachine.js only clears the meaningless
    // samplerate/bitdepth fields when the queue item says webradio, and only
    // then does the UI report MPD's measured bitrate instead of a static
    // database label.
    service: 'webradio',
    type: 'webradio',
    // Deliberately no samplerate/bitdepth: a lossy stream has neither, and
    // station.bitrate is a community-maintained field that is often stale or 0.
    // Let MPD measure it.

Verified on three stations (UI state vs. mpd status, 9 s after replaceAndPlay):

Station Database says UI showed before UI shows now MPD measures
MANGORADIO 128 192 kbps 128 kbps 128
Classic Vinyl HD 320 192 kbps 192 kbps 224 (VBR, varies)
France Info 192 192 kbps 194 kbps 194

Note the second row: the database claims 320, the stream actually runs around 224. That is
exactly the kind of thing you want the player to tell you honestly.

-–

3. While browsing, you cannot see bitrate/codec at all

This one is the flip side of #2. While playing you want the measurement; while browsing
you want the database label, because that is what lets you choose between three entries all
called “KINK” before you click one.

Symptom. 1.0.3 already builds a "192 kbps MP3" string and puts it in album — but on a
default Volumio 4 install you never see it.

Cause. Volumio 4 serves the Manifest skin by default (first entry of
/volumio/volumioUisList.json, unless /data/active\_volumio\_ui says otherwise). Manifest renders
browse results two ways:

  • music-item.html (list view) shows title / album • artist
  • music-card.html (grid view) shows only title and meta

Grid view is what most people land on, and it reads neither album nor artist. So the string
exists and is simply never rendered.

Patch — populate all three fields in \_mapStationsToVolumio:

// Bitrate and codec here come from the RadioBrowser database: a community-
// maintained label, not a measurement. While browsing that is exactly what you
// want to see, so you can choose up front. During playback the measured value
// from MPD wins -- see explodeUri.
var bitrateStr = (station.bitrate \&\& station.bitrate > 0) ? station.bitrate + ' kbps' : '';
var codecStr = station.codec || '';
var bitrateCodec = \[bitrateStr, codecStr].filter(Boolean).join(' ');
var country = station.country || '';
// Tags can be dozens of comma-separated items long; three is enough
// to keep the line from running away.
var tags = (station.tags || '').split(',').filter(Boolean).slice(0, 3).join(', ');
// The Manifest UI renders two shapes: list view (music-item.html) shows
// title / album / artist, grid view (music-card.html) shows only title + meta.
// That is why the bitrate goes into both.
items.push({
    service: 'radio\_browser',
    type: 'webradio',
    title: station.name,
    album: bitrateCodec,
    artist: \[country, tags].filter(Boolean).join(' · '),
    meta: \[bitrateCodec, country].filter(Boolean).join(' · '),
    albumart: station.favicon || undefined,
    icon: station.favicon ? undefined : 'fa fa-microphone',
    uri: baseUri + '/station/' + station.stationuuid
});

Result while browsing (search, Top Voted and Top Clicked all go through this same mapper):

101.9 KINK FM          64 kbps MP3    The United States Of America
KINK                   192 kbps MP3   The Netherlands
KINK                   96 kbps AAC+   The Netherlands · alternative
Exclusively The Kinks  128 kbps MP3   The United Arab Emirates · 60s-90s, album rock, am pop

-–

Applying and reverting

ssh volumio@<your-player>
cd /data/plugins/music\_service/radio\_browser
cp -a index.js index.js.bak-$(date +%Y%m%d)     # keep a copy first
# ...edit index.js...
node --check index.js                            # syntax check before restarting
sudo systemctl restart volumio

Two practical notes:

  • node --check refuses a file whose extension it does not know, so stage edits as
    something.js, not index.js.new.
  • A plugin update wipes all of this, since these are edits to the installed file. Keep the
    backup, and re-apply after every update.

-–

In short

  • explodeUri must resolve an array — this is what silently breaks favourites and playlists,
    and it also forces the small change in clearAddPlayTracks.
  • service: 'webradio' (not 'mpd') and no hardcoded samplerate/bitdepth — then the player
    reports what is actually playing.
  • Put the browse-time bitrate/codec in meta as well as album, or the default Manifest grid
    view will never show it.

Happy to test anything, provide logs, or turn this into a proper PR if someone can point me at
the right repository. Thanks for the plugin — with these three fixes it is genuinely the nicest
way to find radio on Volumio.