mirror of
https://github.com/clementine-player/Clementine
synced 2025-01-27 09:41:32 +01:00
parent
5d60cc33aa
commit
c2c3c8145b
@ -119,6 +119,7 @@ set(SOURCES
|
||||
playlistparsers/xspfparser.cpp
|
||||
|
||||
radio/fixlastfm.cpp
|
||||
radio/icecastservice.cpp
|
||||
radio/lastfmconfig.cpp
|
||||
radio/lastfmservice.cpp
|
||||
radio/lastfmstationdialog.cpp
|
||||
@ -282,6 +283,7 @@ set(HEADERS
|
||||
playlistparsers/plsparser.h
|
||||
playlistparsers/xspfparser.h
|
||||
|
||||
radio/icecastservice.h
|
||||
radio/lastfmconfig.h
|
||||
radio/lastfmservice.h
|
||||
radio/lastfmstationdialog.h
|
||||
|
@ -398,8 +398,9 @@ void Player::EngineMetadataReceived(const Engine::SimpleMetaBundle& bundle) {
|
||||
bundle_copy.title = bundle_copy.title.left(dash_pos).trimmed();
|
||||
}
|
||||
|
||||
// Hack as SomaFM's artist/title descriptions are backwards.
|
||||
if (item->Url().host().contains("somafm.com")) {
|
||||
// Hack as SomaFM's and icecast's artist/title descriptions are backwards.
|
||||
if (item->Url().host().contains("somafm.com") ||
|
||||
item->Url().fragment() == "icecast") {
|
||||
qSwap(bundle_copy.artist, bundle_copy.title);
|
||||
}
|
||||
|
||||
|
170
src/radio/icecastservice.cpp
Normal file
170
src/radio/icecastservice.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
#include "icecastservice.h"
|
||||
|
||||
#include <algorithm>
|
||||
using std::sort;
|
||||
using std::unique;
|
||||
|
||||
#include <QFutureWatcher>
|
||||
#include <QMultiHash>
|
||||
#include <QNetworkReply>
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
#include "core/network.h"
|
||||
|
||||
const char* IcecastService::kServiceName = "Icecast";
|
||||
const char* IcecastService::kDirectoryUrl = "http://dir.xiph.org/yp.xml";
|
||||
|
||||
IcecastService::IcecastService(RadioModel* parent)
|
||||
: RadioService(kServiceName, parent),
|
||||
network_(new NetworkAccessManager(this)) {
|
||||
}
|
||||
|
||||
IcecastService::~IcecastService() {
|
||||
}
|
||||
|
||||
RadioItem* IcecastService::CreateRootItem(RadioItem* parent) {
|
||||
root_ = new RadioItem(this, RadioItem::Type_Service, kServiceName, parent);
|
||||
root_->icon = QIcon(":last.fm/icon_radio.png");
|
||||
return root_;
|
||||
}
|
||||
|
||||
void IcecastService::LazyPopulate(RadioItem* item) {
|
||||
switch (item->type) {
|
||||
case RadioItem::Type_Service:
|
||||
LoadDirectory();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IcecastService::LoadDirectory() {
|
||||
QNetworkRequest req = QNetworkRequest(QUrl(kDirectoryUrl));
|
||||
QNetworkReply* reply = network_->get(req);
|
||||
connect(reply, SIGNAL(finished()), SLOT(DownloadDirectoryFinished()));
|
||||
}
|
||||
|
||||
void IcecastService::DownloadDirectoryFinished() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
Q_ASSERT(reply);
|
||||
|
||||
QFuture<QList<Station> > future =
|
||||
QtConcurrent::run(this, &IcecastService::ParseDirectory, reply);
|
||||
QFutureWatcher<QList<Station> >* watcher =
|
||||
new QFutureWatcher<QList<Station> >(this);
|
||||
watcher->setFuture(future);
|
||||
connect(watcher, SIGNAL(finished()), SLOT(ParseDirectoryFinished()));
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
struct GenreSorter {
|
||||
GenreSorter(const QMultiHash<QString, T>& genres)
|
||||
: genres_(genres) {
|
||||
}
|
||||
|
||||
bool operator() (const QString& a, const QString& b) const {
|
||||
return genres_.count(a) > genres_.count(b);
|
||||
}
|
||||
|
||||
private:
|
||||
const QMultiHash<QString, T>& genres_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct StationSorter {
|
||||
bool operator() (T a, T b) const {
|
||||
return a->name.compare(b->name, Qt::CaseInsensitive) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct StationEquality {
|
||||
bool operator() (T a, T b) const {
|
||||
return a->name == b->name;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void IcecastService::ParseDirectoryFinished() {
|
||||
QFutureWatcher<QList<Station> >* watcher =
|
||||
static_cast<QFutureWatcher<QList<Station> >*>(sender());
|
||||
const QList<Station>& all_stations = watcher->result();
|
||||
|
||||
// Cluster stations by genre.
|
||||
QMultiHash<QString, const Station*> genres;
|
||||
foreach (const Station& s, all_stations) {
|
||||
foreach (const QString& genre, s.genres) {
|
||||
genres.insert(genre, &s);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort genres by station count.
|
||||
QList<QString> genre_names = genres.keys().toSet().toList();
|
||||
sort(genre_names.begin(), genre_names.end(), GenreSorter<const Station*>(genres));
|
||||
|
||||
foreach (const QString& genre, genre_names) {
|
||||
RadioItem* genre_item = new RadioItem(this, Type_Genre, genre);
|
||||
genre_item->icon = QIcon(":last.fm/icon_tag.png");
|
||||
|
||||
QList<const Station*> stations = genres.values(genre);
|
||||
sort(stations.begin(), stations.end(), StationSorter<const Station*>());
|
||||
// Remove duplicates by name. These tend to be multiple URLs for the same station.
|
||||
QList<const Station*>::iterator it =
|
||||
unique(stations.begin(), stations.end(), StationEquality<const Station*>());
|
||||
stations.erase(it, stations.end());
|
||||
foreach (const Station* station, stations) {
|
||||
RadioItem* radio = new RadioItem(
|
||||
this, Type_Stream, station->url.toString(), genre_item);
|
||||
radio->lazy_loaded = true;
|
||||
radio->playable = true;
|
||||
radio->icon = QIcon(":last.fm/icon_radio.png");
|
||||
radio->display_text = station->name;
|
||||
}
|
||||
genre_item->InsertNotify(root_);
|
||||
}
|
||||
|
||||
root_->lazy_loaded = true;
|
||||
delete watcher;
|
||||
}
|
||||
|
||||
QList<IcecastService::Station> IcecastService::ParseDirectory(QIODevice* device) const {
|
||||
QXmlStreamReader reader(device);
|
||||
QList<Station> stations;
|
||||
while (!reader.atEnd()) {
|
||||
reader.readNext();
|
||||
if (reader.tokenType() == QXmlStreamReader::StartElement &&
|
||||
reader.name() == "entry") {
|
||||
stations << ReadStation(&reader);
|
||||
}
|
||||
}
|
||||
device->deleteLater();
|
||||
return stations;
|
||||
}
|
||||
|
||||
IcecastService::Station IcecastService::ReadStation(QXmlStreamReader* reader) const {
|
||||
Station station;
|
||||
while (!reader->atEnd()) {
|
||||
reader->readNext();
|
||||
if (reader->tokenType() == QXmlStreamReader::EndElement)
|
||||
break;
|
||||
|
||||
if (reader->tokenType() == QXmlStreamReader::StartElement) {
|
||||
QStringRef name = reader->name();
|
||||
QString value = reader->readElementText(QXmlStreamReader::SkipChildElements);
|
||||
|
||||
if (name == "server_name") station.name = value;
|
||||
if (name == "listen_url") station.url = QUrl(value);
|
||||
if (name == "server_type") station.mime_type = value;
|
||||
if (name == "bitrate") station.bitrate = value.toInt();
|
||||
if (name == "channels") station.channels = value.toInt();
|
||||
if (name == "samplerate") station.samplerate = value.toInt();
|
||||
if (name == "genre") station.genres = value.split(' ', QString::SkipEmptyParts);
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: This hints to the player that the artist and title metadata needs swapping.
|
||||
station.url.setFragment("icecast");
|
||||
|
||||
return station;
|
||||
}
|
55
src/radio/icecastservice.h
Normal file
55
src/radio/icecastservice.h
Normal file
@ -0,0 +1,55 @@
|
||||
#ifndef ICECASTSERVICE_H
|
||||
#define ICECASTSERVICE_H
|
||||
|
||||
#include "radioservice.h"
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
class NetworkAccessManager;
|
||||
|
||||
class IcecastService : public RadioService {
|
||||
Q_OBJECT
|
||||
public:
|
||||
IcecastService(RadioModel* parent);
|
||||
~IcecastService();
|
||||
|
||||
RadioItem* CreateRootItem(RadioItem* parent);
|
||||
void LazyPopulate(RadioItem* item);
|
||||
|
||||
static const char* kServiceName;
|
||||
static const char* kDirectoryUrl;
|
||||
|
||||
enum ItemType {
|
||||
Type_Stream = 3000,
|
||||
Type_Genre,
|
||||
};
|
||||
|
||||
private:
|
||||
struct Station {
|
||||
Station()
|
||||
: bitrate(0),
|
||||
channels(0),
|
||||
samplerate(0) {
|
||||
}
|
||||
QString name;
|
||||
QUrl url;
|
||||
QString mime_type;
|
||||
int bitrate;
|
||||
int channels;
|
||||
int samplerate;
|
||||
QStringList genres;
|
||||
};
|
||||
|
||||
void LoadDirectory();
|
||||
QList<Station> ParseDirectory(QIODevice* device) const;
|
||||
Station ReadStation(QXmlStreamReader* reader) const;
|
||||
|
||||
RadioItem* root_;
|
||||
NetworkAccessManager* network_;
|
||||
|
||||
private slots:
|
||||
void DownloadDirectoryFinished();
|
||||
void ParseDirectoryFinished();
|
||||
};
|
||||
|
||||
#endif
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include "radiomodel.h"
|
||||
#include "radioservice.h"
|
||||
#include "icecastservice.h"
|
||||
#include "lastfmservice.h"
|
||||
#include "somafmservice.h"
|
||||
#include "radiomimedata.h"
|
||||
@ -44,6 +45,7 @@ RadioModel::RadioModel(BackgroundThread<Database>* db_thread,
|
||||
AddService(new LastFMService(this));
|
||||
AddService(new SomaFMService(this));
|
||||
AddService(new MagnatuneService(this));
|
||||
AddService(new IcecastService(this));
|
||||
AddService(new SavedRadio(this));
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user