From 3e31094227cd34730d4ffe24f760304198d1a56a Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sun, 7 Mar 2021 22:59:32 +0100 Subject: [PATCH] Initial RadioBrowser support: implemented search, groups by category, top 100 --- src/CMakeLists.txt | 3 +- src/core/metatypes.cpp | 3 +- .../radiobrowsersearchprovider.cpp | 64 ++-- src/globalsearch/radiobrowsersearchprovider.h | 26 +- src/internet/core/internetmodel.cpp | 5 + .../radiobrowser/radiobrowserservice.cpp | 318 ++++++++++++------ .../radiobrowser/radiobrowserservice.h | 87 +++-- .../radiobrowser/radiobrowserurlhandler.cpp | 91 ----- .../radiobrowser/radiobrowserurlhandler.h | 50 --- 9 files changed, 318 insertions(+), 329 deletions(-) delete mode 100644 src/internet/radiobrowser/radiobrowserurlhandler.cpp delete mode 100644 src/internet/radiobrowser/radiobrowserurlhandler.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 93451d6a2..0e347ddbe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -193,7 +193,6 @@ set(SOURCES internet/intergalacticfm/intergalacticfmservice.cpp internet/intergalacticfm/intergalacticfmurlhandler.cpp internet/radiobrowser/radiobrowserservice.cpp - internet/radiobrowser/radiobrowserurlhandler.cpp internet/subsonic/subsonicservice.cpp internet/subsonic/subsonicsettingspage.cpp internet/subsonic/subsonicurlhandler.cpp @@ -478,6 +477,7 @@ set(HEADERS globalsearch/searchprovider.h globalsearch/simplesearchprovider.h globalsearch/suggestionwidget.h + globalsearch/radiobrowsersearchprovider.h internet/core/cloudfileservice.h internet/digitally/digitallyimportedclient.h @@ -510,7 +510,6 @@ set(HEADERS internet/intergalacticfm/intergalacticfmservice.h internet/intergalacticfm/intergalacticfmurlhandler.h internet/radiobrowser/radiobrowserservice.h - internet/radiobrowser/radiobrowserurlhandler.h internet/subsonic/subsonicservice.h internet/subsonic/subsonicsettingspage.h internet/subsonic/subsonicurlhandler.h diff --git a/src/core/metatypes.cpp b/src/core/metatypes.cpp index 8312498b5..a6e3c5d1e 100644 --- a/src/core/metatypes.cpp +++ b/src/core/metatypes.cpp @@ -99,8 +99,7 @@ void RegisterMetaTypes() { qRegisterMetaType("SomaFMService::Stream"); qRegisterMetaType( "IntergalacticFMService::Stream"); - qRegisterMetaType( - "RadioBrowserService::Stream"); + qRegisterMetaType("RadioBrowserService::Stream"); qRegisterMetaType("SongList"); qRegisterMetaType("Song"); qRegisterMetaTypeStreamOperators( diff --git a/src/globalsearch/radiobrowsersearchprovider.cpp b/src/globalsearch/radiobrowsersearchprovider.cpp index 3bce35360..5768c7689 100644 --- a/src/globalsearch/radiobrowsersearchprovider.cpp +++ b/src/globalsearch/radiobrowsersearchprovider.cpp @@ -1,5 +1,5 @@ /* This file is part of Clementine. - Copyright 2011, David Sansome + Copyright 2021, Fabio Bas Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,36 +17,50 @@ #include "radiobrowsersearchprovider.h" +#include "ui/iconloader.h" + +namespace { +const int kSearchStationLimit = 10; +} // namespace + RadioBrowserSearchProvider::RadioBrowserSearchProvider( - RadioBrowserServiceBase* service, Application* app, QObject* parent) - : SimpleSearchProvider(app, parent), service_(service) { - Init(service->name(), service->url_scheme(), service->icon(), - CanGiveSuggestions); - set_result_limit(3); - set_max_suggestion_count(3); - icon_ = ScaleAndPad( - service->icon().pixmap(service->icon().availableSizes()[0]).toImage()); - - connect(service, SIGNAL(StreamsChanged()), SLOT(MaybeRecreateItems())); - - // Load the stream list on startup only if it doesn't involve going to update - // info from the server. - if (!service_->IsStreamListStale()) RecreateItems(); + Application* app, RadioBrowserService* service, QObject* parent) + : SearchProvider(app, parent), service_(service) { + Init(RadioBrowserService::kServiceName, "radiobrowser", + IconLoader::Load("radiobrowser", IconLoader::Provider), + WantsDelayedQueries); + connect(service_, + SIGNAL(SearchFinished(int, RadioBrowserService::StreamList)), + SLOT(SearchFinishedSlot(int, RadioBrowserService::StreamList))); } -void RadioBrowserSearchProvider::LoadArtAsync(int id, const Result& result) { - emit ArtLoaded(id, icon_); +void RadioBrowserSearchProvider::SearchAsync(int id, const QString& query) { + PendingState state; + state.orig_id_ = id; + state.tokens_ = TokenizeQuery(query); + + const QString query_string = state.tokens_.join(" "); + service_->Search(id, query_string, kSearchStationLimit); } -void RadioBrowserSearchProvider::RecreateItems() { - QList items; +void RadioBrowserSearchProvider::SearchFinishedSlot( + int search_id, RadioBrowserService::StreamList streams) { + ResultList ret; - for (const RadioBrowserService::Stream& stream : service_->Streams()) { - Item item; - item.metadata_ = stream.ToSong(service_->name()); - item.keyword_ = stream.name_; - items << item; + for (auto stream : streams) { + Result result(this); + result.group_automatically_ = false; + result.metadata_ = stream.ToSong(QString()); + ret << result; } - SetItems(items); + emit ResultsAvailable(search_id, ret); + emit SearchFinished(search_id); } +/* +void RadioBrowserSearchProvider::ShowConfig() { + if (service_) { + return service_->ShowConfig(); + } +} +*/ \ No newline at end of file diff --git a/src/globalsearch/radiobrowsersearchprovider.h b/src/globalsearch/radiobrowsersearchprovider.h index 479e67ba9..7e77f9bdc 100644 --- a/src/globalsearch/radiobrowsersearchprovider.h +++ b/src/globalsearch/radiobrowsersearchprovider.h @@ -1,6 +1,5 @@ - /* This file is part of Clementine. - Copyright 2011, David Sansome + Copyright 2021, Fabio Bas Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,23 +19,24 @@ #define RADIOBROWSERSEARCHPROVIDER_H #include "internet/radiobrowser/radiobrowserservice.h" -#include "simplesearchprovider.h" +#include "searchprovider.h" + +class RadioBrowserSearchProvider : public SearchProvider { + Q_OBJECT -class RadioBrowserSearchProvider : public SimpleSearchProvider { public: - RadioBrowserSearchProvider(RadioBrowserServiceBase* service, - Application* app, QObject* parent); - // SearchProvider + RadioBrowserSearchProvider(Application* app, RadioBrowserService* service, + QObject* parent = nullptr); + void SearchAsync(int id, const QString& query) override; + // void ShowConfig() override; InternetService* internet_service() override { return service_; } - void LoadArtAsync(int id, const Result& result) override; - - protected: - void RecreateItems() override; + public slots: + void SearchFinishedSlot(int search_id, + RadioBrowserService::StreamList streams); private: - RadioBrowserServiceBase* service_; - QImage icon_; + RadioBrowserService* service_; }; #endif // RADIOBROWSERSEARCHPROVIDER_H diff --git a/src/internet/core/internetmodel.cpp b/src/internet/core/internetmodel.cpp index 8c1275c92..623403e12 100644 --- a/src/internet/core/internetmodel.cpp +++ b/src/internet/core/internetmodel.cpp @@ -332,6 +332,11 @@ QMimeData* InternetModel::mimeData(const QModelIndexList& indexes) const { if (urls.isEmpty()) return nullptr; + for (const QModelIndex& index : new_indexes) { + InternetModel::ServiceForIndex(index) + ->ItemNowPlaying(itemFromIndex(index)); + } + InternetMimeData* data = new InternetMimeData(this); data->setUrls(urls); data->indexes = new_indexes; diff --git a/src/internet/radiobrowser/radiobrowserservice.cpp b/src/internet/radiobrowser/radiobrowserservice.cpp index cb1a56648..56675ceba 100644 --- a/src/internet/radiobrowser/radiobrowserservice.cpp +++ b/src/internet/radiobrowser/radiobrowserservice.cpp @@ -1,9 +1,5 @@ /* This file is part of Clementine. - Copyright 2010-2013, David Sansome - Copyright 2011, Tyler Rhodes - Copyright 2011, Paweł Bara - Copyright 2012, 2014, John Maguire - Copyright 2014, Krzysztof Sobiecki + Copyright 2021, Fabio Bas Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,63 +35,140 @@ #include "core/utilities.h" #include "globalsearch/globalsearch.h" #include "globalsearch/radiobrowsersearchprovider.h" -#include "radiobrowserurlhandler.h" -#include "internet/core/internetmodel.h" #include "ui/iconloader.h" -const int RadioBrowserServiceBase::kStreamsCacheDurationSecs = - 60 * 60 * 24 * 28; // 4 weeks - -bool operator<(const RadioBrowserServiceBase::Stream& a, - const RadioBrowserServiceBase::Stream& b) { +bool operator<(const RadioBrowserService::Stream& a, + const RadioBrowserService::Stream& b) { return a.name_.compare(b.name_, Qt::CaseInsensitive) < 0; } -RadioBrowserServiceBase::RadioBrowserServiceBase( - Application* app, InternetModel* parent, const QString& name, - const QUrl& channel_list_url, const QUrl& homepage_url, - const QUrl& donate_page_url, const QIcon& icon) - : InternetService(name, app, parent, parent), - url_scheme_(name.toLower().remove(' ')), - url_handler_(new RadioBrowserUrlHandler(app, this, this)), +const char* RadioBrowserService::kServiceName = "Radio-Browser.info"; +QString RadioBrowserService::SearchUrl = + "%1/json/stations/byname/%2?limit=%3"; +QString RadioBrowserService::PlayClickUrl = "%1/json/url/%2"; + +QList RadioBrowserService::BranchList = { + {"By Country", "%1/json/countries", + "%1/json/stations/bycountryexact/%2?hidebroken=true", Type_Category}, + {"By Language", "%1/json/languages", + "%1/json/stations/bylanguageexact/%2?hidebroken=true", Type_Category}, + {"By Tag", "%1/json/tags", "%1/json/stations/bytagexact/%2?hidebroken=true", + Type_Category}, + {"By Codec", "%1/json/codecs", + "%1/json/stations/bycodecexact/%2?hidebroken=true", Type_Category}, + {"Top 100 Clicked", "", + "%1/json/stations/search?order=clickcount&reverse=true&limit=100", + Type_Top100}, + {"Top 100 Voted", "", + "%1/json/stations/search?order=votes&reverse=true&limit=100", Type_Top100}, + {"Top 100 Trending", "", + "%1/json/stations/search?order=clicktrend&reverse=true&limit=100", + Type_Top100}}; + +RadioBrowserService::RadioBrowserService(Application* app, + InternetModel* parent) + : InternetService(kServiceName, app, parent, parent), root_(nullptr), context_menu_(nullptr), network_(new NetworkAccessManager(this)), - streams_(name, "streams", kStreamsCacheDurationSecs), - name_(name), - channel_list_url_(channel_list_url), - homepage_url_(homepage_url), - donate_page_url_(donate_page_url), - icon_(icon) { + name_(kServiceName), + main_server_url_(QStringLiteral("http://all.api.radio-browser.info")), + homepage_url_(QUrl("https://www.radio-browser.info")), + icon_(IconLoader::Load("radiobrowser", IconLoader::Provider)) { ReloadSettings(); - - app_->player()->RegisterUrlHandler(url_handler_); app_->global_search()->AddProvider( - new RadioBrowserSearchProvider(this, app_, this)); + new RadioBrowserSearchProvider(app_, this, this)); } -RadioBrowserServiceBase::~RadioBrowserServiceBase() { - delete context_menu_; -} +RadioBrowserService::~RadioBrowserService() { delete context_menu_; } -QStandardItem* RadioBrowserServiceBase::CreateRootItem() { +QStandardItem* RadioBrowserService::CreateRootItem() { root_ = new QStandardItem(icon_, name_); root_->setData(true, InternetModel::Role_CanLazyLoad); return root_; } -void RadioBrowserServiceBase::LazyPopulate(QStandardItem* item) { +void RadioBrowserService::LazyPopulate(QStandardItem* item) { switch (item->data(InternetModel::Role_Type).toInt()) { case InternetModel::Type_Service: - RefreshStreams(); + RefreshRootItem(); + break; + case RadioBrowserService::Type_Category: + RefreshCategory(item); + break; + case RadioBrowserService::Type_CategoryItem: + RefreshCategoryItem(item); + break; + case RadioBrowserService::Type_Top100: + RefreshTop100(item); break; - default: break; } } -void RadioBrowserServiceBase::ShowContextMenu(const QPoint& global_pos) { +void RadioBrowserService::RefreshRootItem() { + if (root_->hasChildren()) root_->removeRows(0, root_->rowCount()); + for (auto branch : RadioBrowserService::BranchList) { + QStandardItem* item = new QStandardItem( + IconLoader::Load("icon_radio", IconLoader::Lastfm), QString()); + item->setText(branch.name); + item->setData(branch.type, InternetModel::Role_Type); + item->setData(branch.listUrl, RadioBrowserService::Role_ListUrl); + item->setData(branch.itemsUrl, RadioBrowserService::Role_ItemsUrl); + item->setData(true, InternetModel::Role_CanLazyLoad); + root_->appendRow(item); + } +} + +void RadioBrowserService::RefreshCategory(QStandardItem* item) { + QString determinedUrl = item->data(RadioBrowserService::Role_ListUrl) + .toString() + .arg(main_server_url_); + QUrl url(determinedUrl); + + QNetworkReply* reply = network_->get(QNetworkRequest(url)); + int task_id = app_->task_manager()->StartTask(tr("Getting channels")); + + NewClosure(reply, SIGNAL(finished()), this, + SLOT(RefreshCategoryFinished(QNetworkReply*, int, QStandardItem*)), + reply, task_id, item); +} + +void RadioBrowserService::RefreshCategoryItem(QStandardItem* item) { + QStandardItem* parent = item->parent(); + QString determinedUrl = parent->data(RadioBrowserService::Role_ItemsUrl) + .toString() + .arg(main_server_url_, item->text()); + QUrl url(determinedUrl); + + QNetworkReply* reply = network_->get(QNetworkRequest(url)); + int task_id = app_->task_manager()->StartTask(tr("Getting channels")); + + NewClosure(reply, SIGNAL(finished()), this, + SLOT(RefreshStreamsFinished(QNetworkReply*, int, QStandardItem*)), + reply, task_id, item); +} + +void RadioBrowserService::RefreshTop100(QStandardItem* item) { + QString determinedUrl = item->data(RadioBrowserService::Role_ItemsUrl) + .toString() + .arg(main_server_url_); + QUrl url(determinedUrl); + + QNetworkReply* reply = network_->get(QNetworkRequest(url)); + int task_id = app_->task_manager()->StartTask(tr("Getting channels")); + + NewClosure(reply, SIGNAL(finished()), this, + SLOT(RefreshStreamsFinished(QNetworkReply*, int, QStandardItem*)), + reply, task_id, item); +} + +void RadioBrowserService::ShowContextMenu(const QPoint& global_pos) { + if (!model()->current_index().isValid()) return; + QStandardItem* item = model()->itemFromIndex(model()->current_index()); + if (!item) return; + if (!context_menu_) { context_menu_ = new QMenu; context_menu_->addActions(GetPlaylistActions()); @@ -103,48 +176,40 @@ void RadioBrowserServiceBase::ShowContextMenu(const QPoint& global_pos) { tr("Open %1 in browser").arg(homepage_url_.host()), this, SLOT(Homepage())); - if (!donate_page_url_.isEmpty()) { - context_menu_->addAction(IconLoader::Load("download", IconLoader::Base), - tr("Donate"), this, SLOT(Donate())); - } - context_menu_->addAction(IconLoader::Load("view-refresh", IconLoader::Base), tr("Refresh channels"), this, - SLOT(ForceRefreshStreams())); + SLOT(LazyPopulate(item))); } context_menu_->popup(global_pos); } -void RadioBrowserServiceBase::ForceRefreshStreams() { - QNetworkReply* reply = network_->get(QNetworkRequest(channel_list_url_)); - int task_id = app_->task_manager()->StartTask(tr("Getting channels")); - - NewClosure(reply, SIGNAL(finished()), this, - SLOT(RefreshStreamsFinished(QNetworkReply*, int)), reply, task_id); -} - -void RadioBrowserServiceBase::RefreshStreamsFinished(QNetworkReply* reply, - int task_id) { +void RadioBrowserService::RefreshCategoryFinished(QNetworkReply* reply, + int task_id, + QStandardItem* item) { app_->task_manager()->SetTaskFinished(task_id); reply->deleteLater(); + QJsonDocument document = ParseJsonReply(reply); - if (reply->error() != QNetworkReply::NoError) { - app_->AddError( - tr("Failed to get channel list:\n%1").arg(reply->errorString())); - return; + QStringList list; + QJsonArray contents = document.array(); + qLog(Debug) << "RadioBrowser station list found:" << contents.size(); + for (const QJsonValue& c : contents) { + QJsonObject item = c.toObject(); + list << item["name"].toString(); } + PopulateCategory(item, list); +} + +void RadioBrowserService::RefreshStreamsFinished(QNetworkReply* reply, + int task_id, + QStandardItem* item) { + app_->task_manager()->SetTaskFinished(task_id); + reply->deleteLater(); + QJsonDocument document = ParseJsonReply(reply); + StreamList list; - - QJsonParseError error; - QJsonDocument document = QJsonDocument::fromJson(reply->readAll(), &error); - if (error.error != QJsonParseError::NoError) { - app_->AddError( - tr("Failed to parse channel list:\n%1").arg(error.errorString())); - return; - } - QJsonArray contents = document.array(); qLog(Debug) << "RadioBrowser station list found:" << contents.size(); for (const QJsonValue& c : contents) { @@ -152,25 +217,19 @@ void RadioBrowserServiceBase::RefreshStreamsFinished(QNetworkReply* reply, ReadStation(item, &list); } - streams_.Update(list); - streams_.Sort(); - - // Only update the item's children if it's already been populated - if (!root_->data(InternetModel::Role_CanLazyLoad).toBool()) PopulateStreams(); - - emit StreamsChanged(); + PopulateStreams(item, list); } -void RadioBrowserServiceBase::ReadStation(QJsonObject& item, - StreamList* ret) { +void RadioBrowserService::ReadStation(QJsonObject& item, StreamList* ret) { Stream stream; stream.name_ = item["name"].toString(); + stream.uuid_ = item["stationuuid"].toString(); QUrl url(item["url"].toString()); stream.url_ = url; ret->append(stream); } -Song RadioBrowserServiceBase::Stream::ToSong(const QString& prefix) const { +Song RadioBrowserService::Stream::ToSong(const QString& prefix) const { QString song_title = name_.trimmed(); if (!song_title.startsWith(prefix)) { song_title = prefix + " " + song_title; @@ -184,38 +243,36 @@ Song RadioBrowserServiceBase::Stream::ToSong(const QString& prefix) const { return ret; } -void RadioBrowserServiceBase::Homepage() { +void RadioBrowserService::Homepage() { QDesktopServices::openUrl(homepage_url_); } -void RadioBrowserServiceBase::Donate() { - QDesktopServices::openUrl(donate_page_url_); +PlaylistItem::Options RadioBrowserService::playlistitem_options() const { + return PlaylistItem::PauseDisabled | PlaylistItem::SeekDisabled; } -PlaylistItem::Options RadioBrowserServiceBase::playlistitem_options() const { - return PlaylistItem::PauseDisabled; -} +void RadioBrowserService::PopulateCategory(QStandardItem* parentItem, + QStringList& elements) { + if (parentItem->hasChildren()) + parentItem->removeRows(0, parentItem->rowCount()); -RadioBrowserServiceBase::StreamList RadioBrowserServiceBase::Streams() { - if (IsStreamListStale()) { - metaObject()->invokeMethod(this, "ForceRefreshStreams", - Qt::QueuedConnection); + for (const QString& element : elements) { + QStandardItem* item = new QStandardItem( + IconLoader::Load("icon_radio", IconLoader::Lastfm), QString()); + item->setText(element); + item->setData(RadioBrowserService::Type_CategoryItem, + InternetModel::Role_Type); + item->setData(true, InternetModel::Role_CanLazyLoad); + parentItem->appendRow(item); } - return streams_; } -void RadioBrowserServiceBase::RefreshStreams() { - if (IsStreamListStale()) { - ForceRefreshStreams(); - return; - } - PopulateStreams(); -} +void RadioBrowserService::PopulateStreams(QStandardItem* parentItem, + StreamList& streams) { + if (parentItem->hasChildren()) + parentItem->removeRows(0, parentItem->rowCount()); -void RadioBrowserServiceBase::PopulateStreams() { - if (root_->hasChildren()) root_->removeRows(0, root_->rowCount()); - - for (const Stream& stream : streams_) { + for (const Stream& stream : streams) { QStandardItem* item = new QStandardItem( IconLoader::Load("icon_radio", IconLoader::Lastfm), QString()); item->setText(stream.name_); @@ -223,32 +280,73 @@ void RadioBrowserServiceBase::PopulateStreams() { InternetModel::Role_SongMetadata); item->setData(InternetModel::PlayBehaviour_SingleItem, InternetModel::Role_PlayBehaviour); + item->setData(stream.uuid_, + RadioBrowserService::Role_StationUuid); - root_->appendRow(item); + parentItem->appendRow(item); } } QDataStream& operator<<(QDataStream& out, - const RadioBrowserServiceBase::Stream& stream) { + const RadioBrowserService::Stream& stream) { out << stream.name_ << stream.url_; return out; } QDataStream& operator>>(QDataStream& in, - RadioBrowserServiceBase::Stream& stream) { + RadioBrowserService::Stream& stream) { in >> stream.name_ >> stream.url_; return in; } -void RadioBrowserServiceBase::ReloadSettings() { - streams_.Load(); - streams_.Sort(); +void RadioBrowserService::ReloadSettings() {} + +void RadioBrowserService::Search(int search_id, const QString& query, + const int limit) { + QString determinedUrl = + RadioBrowserService::SearchUrl.arg(main_server_url_, query) + .arg(limit); + QUrl url(determinedUrl); + + QNetworkReply* reply = network_->get(QNetworkRequest(url)); + int task_id = app_->task_manager()->StartTask(tr("Getting channels")); + + NewClosure(reply, SIGNAL(finished()), this, + SLOT(SearchFinishedInternal(QNetworkReply*, int, int)), reply, + task_id, search_id); } -RadioBrowserService::RadioBrowserService(Application* app, - InternetModel* parent) - : RadioBrowserServiceBase( - app, parent, "Radio-Browser.info", - QUrl("http://all.api.radio-browser.info/json/stations"), - QUrl("https://www.radio-browser.info"), QUrl(), - IconLoader::Load("radiobrowser", IconLoader::Provider)) {} +void RadioBrowserService::SearchFinishedInternal(QNetworkReply* reply, + int task_id, + int search_id) { + app_->task_manager()->SetTaskFinished(task_id); + reply->deleteLater(); + QJsonDocument document = ParseJsonReply(reply); + + StreamList list; + QJsonArray contents = document.array(); + qLog(Debug) << "RadioBrowser station list found:" << contents.size(); + for (const QJsonValue& c : contents) { + QJsonObject item = c.toObject(); + ReadStation(item, &list); + } + + emit SearchFinished(search_id, list); +} + +void RadioBrowserService::ItemNowPlaying(QStandardItem* item) { + QString station_uuid = item->data(RadioBrowserService::Role_StationUuid).toString(); + if(station_uuid.isEmpty()) + return; + + QString determinedUrl = + RadioBrowserService::PlayClickUrl.arg(main_server_url_, station_uuid); + QUrl url(determinedUrl); + + qLog(Debug) << "RadioBrowser station played:" << determinedUrl; + QNetworkReply* reply = network_->get(QNetworkRequest(url)); + connect(reply, &QNetworkReply::finished, [this, reply]() + { + reply->deleteLater(); + }); +} \ No newline at end of file diff --git a/src/internet/radiobrowser/radiobrowserservice.h b/src/internet/radiobrowser/radiobrowserservice.h index 153b882e4..2bb1a4bc4 100644 --- a/src/internet/radiobrowser/radiobrowserservice.h +++ b/src/internet/radiobrowser/radiobrowserservice.h @@ -1,7 +1,5 @@ /* This file is part of Clementine. - Copyright 2010-2013, David Sansome - Copyright 2010, 2014, John Maguire - Copyright 2014, Krzysztof Sobiecki + Copyright 2021, Fabio Bas Clementine is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +21,7 @@ #include #include "core/cachedlist.h" +#include "internet/core/internetmodel.h" #include "internet/core/internetservice.h" class RadioBrowserUrlHandler; @@ -31,87 +30,103 @@ class QNetworkAccessManager; class QNetworkReply; class QMenu; -class RadioBrowserServiceBase : public InternetService { +class RadioBrowserService : public InternetService { Q_OBJECT public: - RadioBrowserServiceBase(Application* app, InternetModel* parent, - const QString& name, const QUrl& channel_list_url, - const QUrl& homepage_url, - const QUrl& donate_page_url, const QIcon& icon); - ~RadioBrowserServiceBase(); + RadioBrowserService(Application* app, InternetModel* parent); + ~RadioBrowserService(); enum ItemType { Type_Stream = 2000, }; + enum Type { + Type_Category = InternetModel::TypeCount, + Type_CategoryItem, + Type_Top100, + }; + + enum Role { + Role_ListUrl = InternetModel::RoleCount, + Role_ItemsUrl, + Role_StationUuid, + }; + struct Stream { QString name_; QUrl url_; + QString uuid_; Song ToSong(const QString& prefix) const; }; + + struct Branch { + QString name; + QString listUrl; + QString itemsUrl; + Type type; + }; + + static QList BranchList; + static QString SearchUrl; + static QString PlayClickUrl; + typedef QList StreamList; - static const int kStreamsCacheDurationSecs; + static const char* kServiceName; - const QString& url_scheme() const { return url_scheme_; } const QIcon& icon() const { return icon_; } QStandardItem* CreateRootItem(); - void LazyPopulate(QStandardItem* item); void ShowContextMenu(const QPoint& global_pos); PlaylistItem::Options playlistitem_options() const; - QNetworkAccessManager* network() const { return network_; } void ReloadSettings(); - - bool IsStreamListStale() const { return streams_.IsStale(); } - StreamList Streams(); + void Search(int search_id, const QString& query, const int limit); + void ItemNowPlaying(QStandardItem* item) override; signals: - void StreamsChanged(); + void SearchFinished(int search_id, + RadioBrowserService::StreamList streams); private slots: - void ForceRefreshStreams(); - void RefreshStreams(); - void RefreshStreamsFinished(QNetworkReply* reply, int task_id); + void LazyPopulate(QStandardItem* item); + + void RefreshRootItem(); + void RefreshCategory(QStandardItem* item); + void RefreshCategoryItem(QStandardItem* item); + void RefreshTop100(QStandardItem* item); + + void RefreshCategoryFinished(QNetworkReply* reply, int task_id, + QStandardItem* item); + void RefreshStreamsFinished(QNetworkReply* reply, int task_id, + QStandardItem* item); + void SearchFinishedInternal(QNetworkReply* reply, int task_id, int search_id); void Homepage(); - void Donate(); private: void ReadStation(QJsonObject& value, StreamList* ret); - void PopulateStreams(); + void PopulateCategory(QStandardItem* parentItem, QStringList& elements); + void PopulateStreams(QStandardItem* parentItem, StreamList& streams); private: - const QString url_scheme_; - RadioBrowserUrlHandler* url_handler_; - QStandardItem* root_; QMenu* context_menu_; QNetworkAccessManager* network_; - CachedList streams_; - const QString name_; - const QUrl channel_list_url_; + const QString main_server_url_; const QUrl homepage_url_; - const QUrl donate_page_url_; const QIcon icon_; }; -class RadioBrowserService : public RadioBrowserServiceBase { - public: - RadioBrowserService(Application* app, InternetModel* parent); -}; - QDataStream& operator<<(QDataStream& out, const RadioBrowserService::Stream& stream); -QDataStream& operator>>(QDataStream& in, - RadioBrowserService::Stream& stream); +QDataStream& operator>>(QDataStream& in, RadioBrowserService::Stream& stream); Q_DECLARE_METATYPE(RadioBrowserService::Stream) #endif // INTERNET_RADIOBROWSER_RADIOBROWSERSERVICE_H_ diff --git a/src/internet/radiobrowser/radiobrowserurlhandler.cpp b/src/internet/radiobrowser/radiobrowserurlhandler.cpp deleted file mode 100644 index 408e37397..000000000 --- a/src/internet/radiobrowser/radiobrowserurlhandler.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* This file is part of Clementine. - Copyright 2011-2013, David Sansome - Copyright 2012, Olaf Christ - Copyright 2014, Krzysztof Sobiecki - Copyright 2014, John Maguire - - Clementine is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Clementine is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Clementine. If not, see . -*/ - -#include "radiobrowserurlhandler.h" - -#include -#include -#include -#include - -#include "core/application.h" -#include "core/logging.h" -#include "core/taskmanager.h" -#include "radiobrowserservice.h" -#include "internet/core/internetmodel.h" -#include "playlistparsers/playlistparser.h" - -RadioBrowserUrlHandler::RadioBrowserUrlHandler( - Application* app, RadioBrowserServiceBase* service, QObject* parent) - : UrlHandler(parent), app_(app), service_(service), task_id_(0) {} - -QString RadioBrowserUrlHandler::scheme() const { - return service_->url_scheme(); -} - -QIcon RadioBrowserUrlHandler::icon() const { return service_->icon(); } - -UrlHandler::LoadResult RadioBrowserUrlHandler::StartLoading( - const QUrl& url) { - QUrl playlist_url = url; - playlist_url.setScheme("https"); - - // Load the playlist - QNetworkReply* reply = - service_->network()->get(QNetworkRequest(playlist_url)); - connect(reply, SIGNAL(finished()), SLOT(LoadPlaylistFinished())); - - if (!task_id_) - task_id_ = app_->task_manager()->StartTask(tr("Loading stream")); - - return LoadResult(url, LoadResult::WillLoadAsynchronously); -} - -void RadioBrowserUrlHandler::LoadPlaylistFinished() { - QNetworkReply* reply = qobject_cast(sender()); - app_->task_manager()->SetTaskFinished(task_id_); - task_id_ = 0; - - QUrl original_url(reply->url()); - original_url.setScheme(scheme()); - - if (reply->error() != QNetworkReply::NoError) { - // TODO((David Sansome): Error handling - qLog(Error) << reply->errorString(); - emit AsyncLoadComplete(LoadResult(original_url, LoadResult::NoMoreTracks)); - return; - } - - // Parse the playlist - PlaylistParser parser(nullptr); - QList songs = parser.LoadFromDevice(reply); - - qLog(Info) << "Loading station finished, got" << songs.count() << "songs"; - - // Failed to get playlist? - if (songs.count() == 0) { - qLog(Error) << "Error loading" << scheme() << "playlist"; - emit AsyncLoadComplete(LoadResult(original_url, LoadResult::NoMoreTracks)); - return; - } - - emit AsyncLoadComplete( - LoadResult(original_url, LoadResult::TrackAvailable, songs[0].url())); -} diff --git a/src/internet/radiobrowser/radiobrowserurlhandler.h b/src/internet/radiobrowser/radiobrowserurlhandler.h deleted file mode 100644 index 855636565..000000000 --- a/src/internet/radiobrowser/radiobrowserurlhandler.h +++ /dev/null @@ -1,50 +0,0 @@ -/* This file is part of Clementine. - Copyright 2011-2013, David Sansome - Copyright 2014, Krzysztof Sobiecki - Copyright 2014, John Maguire - - Clementine is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Clementine is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Clementine. If not, see . -*/ - -#ifndef INTERNET_RADIOBROWSER_RADIOBROWSERURLHANDLER_H_ -#define INTERNET_RADIOBROWSER_RADIOBROWSERURLHANDLER_H_ - -#include "core/urlhandler.h" - -class Application; -class RadioBrowserServiceBase; - -class RadioBrowserUrlHandler : public UrlHandler { - Q_OBJECT - - public: - RadioBrowserUrlHandler(Application* app, - RadioBrowserServiceBase* service, - QObject* parent); - - QString scheme() const; - QIcon icon() const; - LoadResult StartLoading(const QUrl& url); - - private slots: - void LoadPlaylistFinished(); - - private: - Application* app_; - RadioBrowserServiceBase* service_; - - int task_id_; -}; - -#endif // INTERNET_RADIOBROWSER_RADIOBROWSERURLHANDLER_H_