Skip to content

Add internet radio services and Radio Browser search#2048

Merged
jonaski merged 1 commit intostrawberrymusicplayer:masterfrom
mzilinski:feature/radio-services
May 3, 2026
Merged

Add internet radio services and Radio Browser search#2048
jonaski merged 1 commit intostrawberrymusicplayer:masterfrom
mzilinski:feature/radio-services

Conversation

@mzilinski
Copy link
Copy Markdown
Contributor

@mzilinski mzilinski commented Mar 27, 2026

Summary

  • Add Radio Browser (radio-browser.info) integration with DNS server discovery, search API, and a tabbed search UI with country/genre filters and pagination — providing access to 50,000+ internet radio stations
  • Add Radio France service (23 channels: FIP, France Musique, France Inter, France Culture)
  • Add CBC Radio service (13 channels: CBC Music, Radio One, ICI Musique/Première)
  • Add Radio settings page with configurable SomaFM stream quality (was hardcoded to "highest") and Radio Browser defaults (search limit, sort order, country, hide broken)
  • Auto-detect newly added services and refresh channels automatically instead of requiring manual database deletion
  • Fix double-click on service nodes in RadioView to expand/collapse instead of adding all channels to playlist

Implementation details

  • RadioBrowserService follows the existing RadioService pattern with DNS-based server discovery and fallback servers
  • RadioBrowserSearchView provides search, filters, and pagination in a new "Radio Browser" tab alongside the existing "Channels" tab
  • Radio France, and CBC use hardcoded stream URLs (no API dependency)
  • RadioService base class gains ExtractJsonArray() for services returning JSON arrays
  • Settings stored via QSettings with constants in dedicated headers
  • API design inspired by Shortwave (GPL-3.0)

Known limitations

  • Icon files are placeholders (copies of radio.png) — proper icons needed
  • CBC stream URLs may change as CBC periodically updates them

Test plan

  • Build and verify no compiler warnings
  • Launch Strawberry, verify new services appear in Channels tab after refresh
  • Test Radio Browser search with various queries, countries, and sort orders
  • Test playback of Radio France, and CBC streams
  • Verify SomaFM quality setting works (Settings → Radio → SomaFM)
  • Verify double-click on service node expands instead of adding all channels
  • Test fresh install (no existing database)

🤖 Generated with Claude Code

@jonaski
Copy link
Copy Markdown
Member

jonaski commented Apr 6, 2026

Crashed when adding a radio to the playlist here:

19:47:36.492 WARN  unknown                          QVariant::save: unable to save type 'RadioChannel' (type id: 65586). 
19:47:36.492 WARN  unknown                           
19:47:36.492 ERROR unknown                          ASSERT failure in QVariant::save: "Invalid type to save", file /home/jonas/Projects/Qt/qtbase/qtbase/src/corelib/kernel/qvariant.cpp, line 1479 
Aborted                    (core dumped) ./strawberry

Comment thread src/radios/cbcservice.cpp Outdated
Comment thread src/radios/radiobrowsersearchview.cpp Outdated
Comment thread src/radios/radiobrowsersearchview.cpp Outdated
Comment thread src/radios/radiobrowserservice.cpp Outdated
Comment thread src/radios/radiobrowserservice.cpp Outdated
Comment thread src/settings/settingsdialog.cpp Outdated
Comment thread src/settings/radiosettingspage.cpp Outdated
Comment thread src/radios/radiobrowsersearchview.cpp Outdated
@mzilinski
Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, really appreciated!

@mzilinski mzilinski requested a review from jonaski April 6, 2026 19:17
@jonaski
Copy link
Copy Markdown
Member

jonaski commented Apr 11, 2026

Some things I notice when testing here:

  • Country, tag and codec is always blank
  • When sorting by name, it's not in ascending order
  • Channels are always refreshed on startup now, causing a delay

Comment thread data/icons.qrc Outdated
Comment thread src/radios/radiobrowsersearchview.cpp Outdated
Comment thread src/radios/radiobrowsersearchview.cpp Outdated
@mzilinski
Copy link
Copy Markdown
Contributor Author

Hi @jonaski, thanks again for the thorough review! I've pushed two commits addressing all your feedback:

  • b768487 — Removed CBC/RadioFrance icons, fixed blank country/tags/codec columns, fixed ascending name sort, fixed startup delay from channel refresh
  • b444635 — Refactored from QStandardItemModel to QAbstractTableModel (fixes the QVariant::save crash), deferred FetchCountries() to showEvent for lazy init, extracted CountryList() as static method to deduplicate QLocale logic

Would appreciate another review when you get a chance!

@mzilinski mzilinski requested a review from jonaski April 13, 2026 19:47
@jonaski
Copy link
Copy Markdown
Member

jonaski commented Apr 21, 2026

Sorry for the delay in getting back on this but it's been quite busy.
I see now you addressed my comment, I will test again.

@mzilinski
Copy link
Copy Markdown
Contributor Author

@jonaski Could you re-run the FreeBSD job for the latest CI run (24883340558)? It failed with an infrastructure issue, not a code issue — the FreeBSD VM couldn't reach the pkg repository at startup:

pkg: Repository FreeBSD-ports cannot be opened. 'pkg update' required
pkg: No packages available to install matching 'git' have been found in the repositories
... (same for all other build dependencies)

So no packages could be installed and the job aborted before any build step ran. All other platforms (Linux, macOS, Windows, OpenBSD) passed. I don't have admin rights to re-run it myself. Thanks!

@mzilinski mzilinski force-pushed the feature/radio-services branch from 5c88c9b to 82a4bb0 Compare April 28, 2026 17:26
@mzilinski
Copy link
Copy Markdown
Contributor Author

Hi @jonaski, no rush at all and totally understand things are busy on your end!

Just a small update: I rebased the branch onto master to clean up the history (now linear, no merge commits, in line with CONTRIBUTING.md), so the "rebase and merge" option works as well now.

Whenever you find a moment, is there anything else you'd like me to address, or were you able to finish the second round of testing? Also gentle reminder on the FreeBSD CI re-run when convenient — it failed only due to an infrastructure issue (pkg repo unreachable).

Thanks again for all your time on this!

@jonaski jonaski force-pushed the feature/radio-services branch from 82a4bb0 to a9f5e9b Compare May 3, 2026 19:25
@jonaski jonaski requested a review from Copilot May 3, 2026 19:25
@jonaski
Copy link
Copy Markdown
Member

jonaski commented May 3, 2026

I've squashed the commits and I fixed a couple of things, added radio browser to Song::is_radio(), made Column enum class (https://github.com/strawberrymusicplayer/strawberry/compare/82a4bb0fa04c5f8536352e21b3d305cc389369eb..a9f5e9b3e915b28786b069d5e8ca5e03a9e8319e).
I'll just check if @copilot points out anything worthwhile, if not I'll merge it.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds multiple internet radio integrations (notably Radio Browser search) plus a new Radio settings page and radio UI improvements.

Changes:

  • Introduces Radio Browser service (DNS discovery + search API) and a new tabbed “Radio Browser” search UI.
  • Adds new radio settings for SomaFM quality and Radio Browser defaults.
  • Updates radio/song plumbing (new Song::Source, playlist handling, cover handling) and fixes double-click behavior on service nodes.

Reviewed changes

Copilot reviewed 33 out of 39 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/utilities/coverutils.cpp Treat Radio Browser sources like other stream sources for cover filename hashing.
src/settings/settingsdialog.h Adds a new Settings dialog page enum for Radio.
src/settings/settingsdialog.cpp Registers the new Radio settings page in the dialog.
src/settings/radiosettingspage.ui New UI for SomaFM + Radio Browser settings.
src/settings/radiosettingspage.h Declares RadioSettingsPage and country list helpers.
src/settings/radiosettingspage.cpp Implements settings load/save and country list population.
src/radios/somafmservice.cpp Reads SomaFM preferred stream quality from settings instead of hardcoding.
src/radios/radioviewcontainer.ui Reworks the radio view into a tabbed UI, adding Radio Browser search tab.
src/radios/radioviewcontainer.h Exposes search_view() accessor for the new tab widget.
src/radios/radioview.h Adds double-click handler override to customize service-node behavior.
src/radios/radioview.cpp Implements double-click expand/collapse for service nodes.
src/radios/radioservices.cpp Registers RadioBrowserService and tweaks refresh state handling.
src/radios/radioservice.h Adds JSON array extraction helpers for services returning arrays.
src/radios/radioservice.cpp Implements ExtractJsonArray() parsing and validation.
src/radios/radiomodel.h Adds sources() accessor to enumerate available sources.
src/radios/radiochannel.h Extends RadioChannel with metadata fields + QDataStream operators.
src/radios/radiobrowserservice.h New Radio Browser backend service interface and signals.
src/radios/radiobrowserservice.cpp Implements DNS discovery, fallback server testing, search, and country fetch.
src/radios/radiobrowsersearchview.ui New UI for search field, filters, results table, and pagination.
src/radios/radiobrowsersearchview.h Declares the search view widget, slots, and playlist integration signal.
src/radios/radiobrowsersearchview.cpp Implements search UI logic, settings defaults, and playlist actions.
src/radios/radiobrowsersearchmodel.h New table model for displaying search results and drag support.
src/radios/radiobrowsersearchmodel.cpp Implements result model, headers, and mime data creation.
src/playlist/playlistitem.cpp Treats Radio Browser like other radio streams for playlist items.
src/covermanager/albumcoverchoicecontroller.cpp Excludes Radio Browser from manual cover saving like other stream sources.
src/core/song.h Adds Song::Source::RadioBrowser and updates kSourceCount.
src/core/song.cpp Updates radio source checks, display strings, icons, domain, share URL, cache dir.
src/core/mainwindow.cpp Wires Radio Browser search view to the RadioBrowserService and playlist adding.
src/constants/somafmsettings.h New constants header for SomaFM settings keys/defaults.
src/constants/radioparadisesettings.h New (currently minimal) constants header for Radio Paradise settings.
src/constants/radiobrowsersettings.h New constants header for Radio Browser settings keys/defaults.
data/icons.qrc Adds radiobrowser icon assets to the Qt resource bundle.
CMakeLists.txt Adds new radio sources, headers, and UI forms to the build.
Comments suppressed due to low confidence (4)

src/settings/radiosettingspage.cpp:1

  • Radio Browser settings keys are partly centralized (RadioBrowserSettings::*) but default_sort / default_country are still repeated as string literals. To avoid drift between the settings page and the search view, add constants for these keys in radiobrowsersettings.h and use them consistently in both Load/Save and the search view initialization.
    src/settings/radiosettingspage.cpp:1
  • Radio Browser settings keys are partly centralized (RadioBrowserSettings::*) but default_sort / default_country are still repeated as string literals. To avoid drift between the settings page and the search view, add constants for these keys in radiobrowsersettings.h and use them consistently in both Load/Save and the search view initialization.
    src/radios/radiobrowsersearchview.cpp:1
  • Starting a new search clears the model but doesn’t cancel in-flight requests or guard against stale responses. If the user types quickly (debounced by search_timer_) or changes filters while a previous request is still running, an older SearchFinished can arrive later and append results for the wrong query into the cleared model. Consider aborting outstanding search replies when triggering a new search (ideally without aborting country loading), or tagging requests with a monotonically increasing search token and ignoring results that don’t match the current token.
/*

src/radios/radioservices.cpp:1

  • channels_refresh_ is only reset to false when channels is non-empty. If a refresh legitimately yields an empty list (or a service returns none), the refresh flag may remain set indefinitely and can lead to incorrect refresh state/behavior. Set channels_refresh_ = false for the successful completion path regardless of channel count (e.g., move it outside the if/else, or set it in both branches where the backend response is accepted).
/*

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/core/song.h Outdated
RadioBrowser = 12
};
static const int kSourceCount = 16;
static const int kSourceCount = 13;
Comment on lines +111 to +124
if (dns_lookup_->error() != QDnsLookup::NoError || dns_lookup_->hostAddressRecords().isEmpty()) {
// DNS failed, try fallback servers
dns_lookup_->deleteLater();
dns_lookup_ = nullptr;

if (fallback_index_ < kFallbackServers.size()) {
TestServer(kFallbackServers.at(fallback_index_));
++fallback_index_;
}
else {
Q_EMIT SearchError(tr("Failed to discover Radio Browser server."));
}
return;
}
Comment on lines +160 to +162
++fallback_index_;
if (fallback_index_ < kFallbackServers.size()) {
TestServer(kFallbackServers.at(fallback_index_));
}

// Use first resolved hostname
const auto records = dns_lookup_->hostAddressRecords();
Comment on lines +210 to +217
QUrlQuery url_query;
if (!query.isEmpty()) url_query.addQueryItem(u"name"_s, query);
if (!country.isEmpty()) url_query.addQueryItem(u"countrycode"_s, country);
if (!tag.isEmpty()) url_query.addQueryItem(u"tag"_s, tag);
if (!language.isEmpty()) url_query.addQueryItem(u"language"_s, language);
url_query.addQueryItem(u"limit"_s, QString::number(limit));
url_query.addQueryItem(u"offset"_s, QString::number(offset));
url_query.addQueryItem(u"hidebroken"_s, u"true"_s);
Comment on lines +241 to +252
void RadioBrowserService::SearchReply(QNetworkReply *reply, const int task_id, const int limit) {

if (replies_.contains(reply)) replies_.removeAll(reply);
reply->deleteLater();

const QJsonArray array = ExtractJsonArray(reply);
task_manager_->SetTaskFinished(task_id);

if (array.isEmpty()) {
Q_EMIT SearchFinished(RadioChannelList(), false);
return;
}
Comment on lines +112 to +115
if (json_error.error != QJsonParseError::NoError) {
Error(QStringLiteral("Failed to parse Json data from %1: %2").arg(name_, json_error.errorString()));
return QJsonArray();
}
Comment on lines +122 to +125
if (!json_document.isArray()) {
Error(QStringLiteral("%1: Json document is not an array.").arg(name_), json_document);
return QJsonArray();
}
@jonaski jonaski force-pushed the feature/radio-services branch from a9f5e9b to 34c6498 Compare May 3, 2026 19:56
@jonaski jonaski merged commit c601972 into strawberrymusicplayer:master May 3, 2026
25 of 26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants